#!/usr/bin/python3 | |
# SPDX-License-Identifier: BSD-2-Clause-Patent | |
import os | |
import sys | |
import json | |
import shutil | |
import pprint | |
import argparse | |
import subprocess | |
def openssl_configure(openssldir, target, ec = True): | |
""" Run openssl Configure script. """ | |
cmdline = [ | |
'perl', | |
'Configure', | |
'--config=../UefiAsm.conf', | |
'--api=1.1.1', | |
'--with-rand-seed=none', | |
target, | |
'no-afalgeng', | |
'no-aria', | |
'no-async', | |
'no-autoerrinit', | |
'no-autoload-config', | |
'no-bf', | |
'no-blake2', | |
'no-camellia', | |
'no-capieng', | |
'no-cast', | |
'no-chacha', | |
'no-cmac', | |
'no-cmp', | |
'no-cms', | |
'no-ct', | |
'no-deprecated', | |
'no-des', | |
'no-dgram', | |
'no-dsa', | |
'no-dso', | |
'no-dtls', | |
'no-dtls1-method', | |
'no-dtls1_2-method', | |
'no-dynamic-engine', | |
'no-ec2m', | |
'no-engine', | |
'no-err', | |
'no-filenames', | |
'no-gost', | |
'no-hw', | |
'no-idea', | |
'no-ktls', | |
'no-makedepend', | |
'no-module', | |
'no-md4', | |
'no-mdc2', | |
'no-multiblock', | |
'no-nextprotoneg', | |
'no-pic', | |
'no-psk', | |
'no-ocb', | |
'no-ocsp', | |
'no-padlockeng', | |
'no-poly1305', | |
'no-posix-io', | |
'no-rc2', | |
'no-rc4', | |
'no-rc5', | |
'no-rfc3779', | |
'no-rmd160', | |
'no-scrypt', | |
'no-seed', | |
'no-shared', | |
'no-siphash', | |
'no-siv', | |
'no-sm2', | |
'no-sm4', | |
'no-sock', | |
'no-srp', | |
'no-srtp', | |
'no-ssl', | |
'no-ssl3-method', | |
'no-ssl-trace', | |
'no-static-engine', | |
'no-stdio', | |
'no-threads', | |
'no-tls1_3', | |
'no-ts', | |
'no-ui-console', | |
'no-whirlpool', | |
'disable-legacy', | |
] | |
if not ec: | |
cmdline += [ 'no-ec', ] | |
print('') | |
print(f'# -*- configure openssl for {target} (ec={ec}) -*-') | |
rc = subprocess.run(cmdline, cwd = openssldir, | |
stdout = subprocess.PIPE, | |
stderr = subprocess.PIPE) | |
if rc.returncode: | |
print(rc.stdout) | |
print(rc.stderr) | |
sys.exit(rc.returncode) | |
def openssl_run_make(openssldir, target): | |
""" | |
Run make utility to generate files or cleanup. | |
Target can be either a string or a list of strings. | |
""" | |
cmdline = [ 'make', '--silent' ] | |
if isinstance(target, list): | |
cmdline += target | |
else: | |
cmdline += [ target, ] | |
rc = subprocess.run(cmdline, cwd = openssldir) | |
rc.check_returncode() | |
def get_configdata(openssldir): | |
""" | |
Slurp openssl config data as JSON, | |
using a little perl helper script. | |
""" | |
cmdline = [ | |
'perl', | |
'perl2json.pl', | |
openssldir, | |
] | |
rc = subprocess.run(cmdline, stdout = subprocess.PIPE) | |
rc.check_returncode() | |
return json.loads(rc.stdout) | |
def is_asm(filename): | |
""" Check whenevr the passed file is an assembler file """ | |
if filename.endswith('.s') or filename.endswith('.S'): | |
return True | |
return False | |
def copy_generated_file(src, dst): | |
src_file = [] | |
with open(src, 'r') as fsrc: | |
src_file = fsrc.readlines() | |
with open(dst, 'w') as fdst: | |
for lines in range(len(src_file)): | |
s = src_file[lines] | |
s = s.rstrip() + "\r\n" | |
fdst.write(s.expandtabs()) | |
def generate_files(openssldir, opensslgendir, asm, filelist): | |
""" | |
Generate files, using make, and copy over the results to the | |
directory tree for generated openssl files. Creates | |
subdirectories as needed. | |
""" | |
openssl_run_make(openssldir, filelist) | |
for filename in filelist: | |
src = os.path.join(openssldir, filename) | |
if is_asm(filename): | |
""" rename MSFT asm files to .nasm """ | |
if 'IA32-MSFT' in asm: | |
filename = filename.replace('.S', '.nasm') | |
elif 'X64-MSFT' in asm: | |
filename = filename.replace('.s', '.nasm') | |
dst = os.path.join(opensslgendir, asm, filename) | |
else: | |
dst = os.path.join(opensslgendir, filename) | |
os.makedirs(os.path.dirname(dst), exist_ok = True) | |
copy_generated_file(src, dst) | |
def generate_include_files(openssldir, opensslgendir, asm, cfg): | |
""" Generate openssl include files """ | |
print('# generate include files') | |
filelist = cfg['unified_info']['generate'].keys() | |
filelist = list(filter(lambda f: 'include' in f, filelist)) | |
generate_files(openssldir, opensslgendir, asm, filelist) | |
def generate_library_files(openssldir, opensslgendir, asm, cfg, obj): | |
""" | |
Generate openssl source files for a given library. Handles | |
mostly assembler files, but a few C sources are generated too. | |
""" | |
filelist = get_source_list(cfg, obj, True) | |
if filelist: | |
print(f'# generate source files for {obj}') | |
generate_files(openssldir, opensslgendir, asm, filelist) | |
def generate_all_files(openssldir, opensslgendir, asm, cfg): | |
""" Generate all files needed. """ | |
generate_include_files(openssldir, opensslgendir, asm, cfg) | |
generate_library_files(openssldir, opensslgendir, asm, cfg, 'libcrypto') | |
generate_library_files(openssldir, opensslgendir, asm, cfg, 'providers/libcommon.a') | |
generate_library_files(openssldir, opensslgendir, asm, cfg, 'libssl') | |
def get_source_list(cfg, obj, gen): | |
""" | |
Gets the list of source files needed to create a specific object. | |
* If 'gen' is True the function returns the list of generated | |
files. | |
* If 'gen' is False the function returns the list of files not | |
generated (which are used from the submodule directly). | |
Note: Will call itself recursively to resolve nested dependencies. | |
""" | |
sources = cfg['unified_info']['sources'] | |
generate = cfg['unified_info']['generate'] | |
srclist = [] | |
if sources.get(obj): | |
for item in sources.get(obj): | |
srclist += get_source_list(cfg, item, gen) | |
else: | |
is_generated = generate.get(obj) is not None | |
if is_generated == gen: | |
srclist += [ obj, ] | |
return srclist | |
def asm_filter_fn(filename): | |
""" | |
Filter asm source and define lists. Drops files we don't want include. | |
""" | |
exclude = [ | |
'/bn/', | |
'OPENSSL_BN_ASM', | |
'OPENSSL_IA32_SSE2', | |
'/ec/', | |
'ECP_NISTZ256_ASM', | |
'X25519_ASM', | |
] | |
for item in exclude: | |
if item in filename: | |
return False | |
return True | |
def get_sources(cfg, obj, asm): | |
""" | |
Get the list of all sources files. Will fetch both generated | |
and not generated file lists and update the paths accordingly, so | |
the openssl submodule or the sub-tree for generated files is | |
referenced as needed. | |
""" | |
srclist = get_source_list(cfg, obj, False) | |
genlist = get_source_list(cfg, obj, True) | |
srclist = list(map(lambda x: f'$(OPENSSL_PATH)/{x}', srclist)) | |
c_list = list(map(lambda x: f'$(OPENSSL_GEN_PATH)/{x}', | |
filter(lambda x: not is_asm(x), genlist))) | |
asm_list = list(map(lambda x: f'$(OPENSSL_GEN_PATH)/{asm}/{x}', | |
filter(is_asm, genlist))) | |
asm_list = list(filter(asm_filter_fn, asm_list)) | |
return srclist + c_list + asm_list | |
def sources_filter_fn(filename): | |
""" | |
Filter source lists. Drops files we don't want include or | |
need replace with our own uefi-specific version. | |
""" | |
exclude = [ | |
'randfile.c', | |
'/store/', | |
'/storemgmt/', | |
'/encode_decode/encode', | |
'/pkcs12/', | |
'statem_srvr.c', | |
'extensions_srvr.c', | |
'defltprov.c', | |
'baseprov.c', | |
'provider_predefined.c', | |
'ecp_nistz256.c', | |
'x86_64-gcc.c', | |
] | |
for item in exclude: | |
if item in filename: | |
return False | |
return True | |
def libcrypto_sources(cfg, asm = None): | |
""" Get source file list for libcrypto """ | |
files = get_sources(cfg, 'libcrypto', asm) | |
files += get_sources(cfg, 'providers/libcommon.a', asm) | |
files = list(filter(sources_filter_fn, files)) | |
return files | |
def libssl_sources(cfg, asm = None): | |
""" Get source file list for libssl """ | |
files = get_sources(cfg, 'libssl', asm) | |
files = list(filter(sources_filter_fn, files)) | |
return files | |
def update_inf(filename, sources, arch = None, defines = []): | |
""" | |
Update inf file, replace source file list and build flags. | |
""" | |
head = '' | |
tail = '' | |
state = 0 | |
if arch: | |
section = f'Sources.{arch}' | |
flags = f'OPENSSL_FLAGS_{arch}' | |
else: | |
section = None | |
flags = f'OPENSSL_FLAGS_NOASM' | |
state = 1 | |
# read and parse file | |
with open(filename, 'r') as f: | |
while True: | |
line = f.readline() | |
if line == '': | |
break | |
if state in [0, 1]: | |
if flags in line: | |
(keep, replace) = line.split('=') | |
args = map(lambda x: f'-D{x}', defines) | |
head += keep + '= ' + ' '.join(args) | |
head = head.rstrip() + '\r\n' | |
else: | |
head += line.rstrip() + '\r\n' | |
if state == 0 and section in line: | |
state = 1 | |
if state == 1 and 'Autogenerated files list starts here' in line: | |
state = 2 | |
if state == 2 and 'Autogenerated files list ends here' in line: | |
state = 3 | |
if state == 3: | |
tail += line.rstrip() + '\r\n' | |
# write updated file | |
with open(filename, 'w') as f: | |
f.write(head) | |
for src in sources: | |
f.write(f' {src}\r\n') | |
f.write(tail) | |
def update_MSFT_asm_format(asm, filelist): | |
""" rename MSFT asm files to .nasm """ | |
if 'IA32-MSFT' in asm: | |
for file_index in range(len(filelist)): | |
filelist[file_index] = filelist[file_index].replace('.S', '.nasm') | |
elif 'X64-MSFT' in asm: | |
for file_index in range(len(filelist)): | |
filelist[file_index] = filelist[file_index].replace('.s', '.nasm') | |
def main(): | |
# prepare | |
os.chdir(os.path.dirname(os.path.abspath(__file__))) | |
openssldir = os.path.join(os.getcwd(), 'openssl') | |
opensslgendir = os.path.join(os.getcwd(), 'OpensslGen') | |
# asm accel configs (see UefiAsm.conf) | |
for ec in [True, False]: | |
if ec: | |
inf = 'OpensslLibFullAccel.inf' | |
hdr = 'configuration-ec.h' | |
else: | |
inf = 'OpensslLibAccel.inf' | |
hdr = 'configuration-noec.h' | |
sources = {} | |
defines = {} | |
for asm in [ 'UEFI-IA32-MSFT', 'UEFI-IA32-GCC', | |
'UEFI-X64-MSFT', 'UEFI-X64-GCC']: | |
(uefi, arch, cc) = asm.split('-') | |
archcc = f'{arch}-{cc}' | |
openssl_configure(openssldir, asm, ec = ec); | |
cfg = get_configdata(openssldir) | |
generate_all_files(openssldir, opensslgendir, archcc, cfg) | |
shutil.move(os.path.join(opensslgendir, 'include', 'openssl', 'configuration.h'), | |
os.path.join(opensslgendir, 'include', 'openssl', hdr)) | |
openssl_run_make(openssldir, 'distclean') | |
srclist = libcrypto_sources(cfg, archcc) + libssl_sources(cfg, archcc) | |
sources[archcc] = list(map(lambda x: f'{x} | {cc}', filter(is_asm, srclist))) | |
update_MSFT_asm_format(archcc, sources[archcc]) | |
sources[arch] = list(filter(lambda x: not is_asm(x), srclist)) | |
defines[arch] = cfg['unified_info']['defines']['libcrypto'] | |
defines[arch] = list(filter(asm_filter_fn, defines[arch])) | |
ia32accel = sources['IA32'] + sources['IA32-MSFT'] + sources['IA32-GCC'] | |
x64accel = sources['X64'] + sources['X64-MSFT'] + sources['X64-GCC'] | |
update_inf(inf, ia32accel, 'IA32', defines['IA32']) | |
update_inf(inf, x64accel, 'X64', defines['X64']) | |
# noaccel - ec enabled | |
openssl_configure(openssldir, 'UEFI', ec = True); | |
cfg = get_configdata(openssldir) | |
generate_all_files(openssldir, opensslgendir, None, cfg) | |
openssl_run_make(openssldir, 'distclean') | |
defines = [] | |
if 'libcrypto' in cfg['unified_info']['defines']: | |
defines = cfg['unified_info']['defines']['libcrypto'] | |
update_inf('OpensslLibFull.inf', | |
libcrypto_sources(cfg) + libssl_sources(cfg), | |
defines) | |
# noaccel - ec disabled | |
openssl_configure(openssldir, 'UEFI', ec = False); | |
cfg = get_configdata(openssldir) | |
generate_all_files(openssldir, opensslgendir, None, cfg) | |
openssl_run_make(openssldir, 'distclean') | |
update_inf('OpensslLibCrypto.inf', | |
libcrypto_sources(cfg), | |
None, defines) | |
update_inf('OpensslLib.inf', | |
libcrypto_sources(cfg) + libssl_sources(cfg), | |
None, defines) | |
# wrap header file | |
confighdr = os.path.join(opensslgendir, 'include', 'openssl', 'configuration.h') | |
with open(confighdr, 'w') as f: | |
f.write('#ifdef EDK2_OPENSSL_NOEC\r\n' | |
'# include "configuration-noec.h"\r\n' | |
'#else\r\n' | |
'# include "configuration-ec.h"\r\n' | |
'#endif\r\n') | |
if __name__ == '__main__': | |
sys.exit(main()) |