1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-10 16:58:28 +03:00

ukify: Add a unified interface for signing tools

This commit is contained in:
Jörg Behrmann 2024-10-10 11:57:12 +02:00
parent 929d6225b2
commit 02eabaffe9
2 changed files with 105 additions and 96 deletions

View File

@ -364,7 +364,7 @@ def test_config_priority(tmp_path):
pathlib.Path('some/path8')]
assert opts.pcr_banks == ['SHA1', 'SHA256']
assert opts.signing_engine == 'ENGINE'
assert opts.signtool == 'sbsign' # from args
assert opts.signtool == ukify.SbSign # from args
assert opts.sb_key == 'SBKEY' # from args
assert opts.sb_cert == 'SBCERT' # from args
assert opts.sb_certdir == 'some/path5' # from config

View File

@ -404,6 +404,81 @@ class UKI:
self.sections += [section]
class SignTool:
@staticmethod
def sign(input_f: str, output_f: str, opts: argparse.Namespace) -> None:
raise NotImplementedError()
@staticmethod
def verify(opts: argparse.Namespace) -> bool:
raise NotImplementedError()
class PeSign(SignTool):
@staticmethod
def sign(input_f: str, output_f: str, opts: argparse.Namespace) -> None:
assert opts.sb_certdir is not None
assert opts.sb_cert_name is not None
tool = find_tool('pesign', opts=opts, msg='pesign, required for signing, is not installed')
cmd = [
tool,
'-s',
'--force',
'-n', opts.sb_certdir,
'-c', opts.sb_cert_name,
'-i', input_f,
'-o', output_f,
] # fmt: skip
print('+', shell_join(cmd))
subprocess.check_call(cmd)
@staticmethod
def verify(opts: argparse.Namespace) -> bool:
assert opts.linux is not None
tool = find_tool('pesign', opts=opts)
cmd = [tool, '-i', opts.linux, '-S']
print('+', shell_join(cmd))
info = subprocess.check_output(cmd, text=True)
return 'No signatures found.' in info
class SbSign(SignTool):
@staticmethod
def sign(input_f: str, output_f: str, opts: argparse.Namespace) -> None:
assert opts.sb_key is not None
assert opts.sb_cert is not None
tool = find_tool('sbsign', opts=opts, msg='sbsign, required for signing, is not installed')
cmd = [
tool,
'--key', opts.sb_key,
'--cert', opts.sb_cert,
*(['--engine', opts.signing_engine] if opts.signing_engine is not None else []),
input_f,
'--output', output_f,
] # fmt: skip
print('+', shell_join(cmd))
subprocess.check_call(cmd)
@staticmethod
def verify(opts: argparse.Namespace) -> bool:
assert opts.linux is not None
tool = find_tool('sbverify', opts=opts)
cmd = [tool, '--list', opts.linux]
print('+', shell_join(cmd))
info = subprocess.check_output(cmd, text=True)
return 'No signature table present' in info
def parse_banks(s: str) -> list[str]:
banks = re.split(r',|\s+', s)
# TODO: do some sanity checking here
@ -798,79 +873,6 @@ def merge_sbat(input_pe: list[Path], input_text: list[str]) -> str:
)
def signer_sign(cmd: list[Union[str, Path]]) -> None:
print('+', shell_join(cmd))
subprocess.check_call(cmd)
def sbsign_sign(
sbsign_tool: Union[str, Path],
input_f: str,
output_f: str,
opts: argparse.Namespace,
) -> None:
sign_invocation = [
sbsign_tool,
'--key', opts.sb_key,
'--cert', opts.sb_cert,
] # fmt: skip
if opts.signing_engine is not None:
sign_invocation += ['--engine', opts.signing_engine]
sign_invocation += [
input_f,
'--output', output_f,
] # fmt: skip
signer_sign(sign_invocation)
def pesign_sign(
pesign_tool: Union[str, Path],
input_f: str,
output_f: str,
opts: argparse.Namespace,
) -> None:
sign_invocation = [
pesign_tool,
'-s',
'--force',
'-n', opts.sb_certdir,
'-c', opts.sb_cert_name,
'-i', input_f,
'-o', output_f,
] # fmt: skip
signer_sign(sign_invocation)
SBVERIFY = {
'name': 'sbverify',
'option': '--list',
'output': 'No signature table present',
}
PESIGCHECK = {
'name': 'pesign',
'option': '-i',
'output': 'No signatures found.',
'flags': '-S',
}
def verify(tool: dict[str, str], opts: argparse.Namespace) -> bool:
verify_tool = find_tool(tool['name'], opts=opts)
cmd = [
verify_tool,
tool['option'],
opts.linux,
]
if 'flags' in tool:
cmd.append(tool['flags'])
print('+', shell_join(cmd))
info = subprocess.check_output(cmd, text=True)
return tool['output'] in info
STUB_SBAT = """\
sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md
uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/
@ -885,32 +887,21 @@ uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/l
def make_uki(opts: argparse.Namespace) -> None:
# kernel payload signing
sign_tool = None
sign_args_present = opts.sb_key or opts.sb_cert_name
sign_kernel = opts.sign_kernel
sign: Optional[Callable[[Union[str, Path], str, str, argparse.Namespace], None]] = None
linux = opts.linux
if sign_args_present:
if opts.signtool == 'sbsign':
sign_tool = find_tool('sbsign', opts=opts, msg='sbsign, required for signing, is not installed')
sign = sbsign_sign
verify_tool = SBVERIFY
else:
sign_tool = find_tool('pesign', opts=opts, msg='pesign, required for signing, is not installed')
sign = pesign_sign
verify_tool = PESIGCHECK
assert opts.signtool is not None
if sign_kernel is None and opts.linux is not None:
if not sign_kernel and opts.linux is not None:
# figure out if we should sign the kernel
sign_kernel = verify(verify_tool, opts)
sign_kernel = opts.signtool.verify(opts)
if sign_kernel:
assert sign is not None
assert sign_tool is not None
linux_signed = tempfile.NamedTemporaryFile(prefix='linux-signed')
linux = Path(linux_signed.name)
sign(sign_tool, opts.linux, linux, opts=opts)
opts.signtool.sign(opts.linux, linux, opts=opts)
if opts.uname is None and opts.linux is not None:
print('Kernel version not specified, starting autodetection 😖.')
@ -1067,9 +1058,8 @@ def make_uki(opts: argparse.Namespace) -> None:
# UKI signing
if sign_args_present:
assert sign is not None
assert sign_tool is not None
sign(sign_tool, unsigned_output, opts.output, opts)
assert opts.signtool is not None
opts.signtool.sign(unsigned_output, opts.output, opts)
# We end up with no executable bits, let's reapply them
os.umask(umask := os.umask(0))
@ -1422,6 +1412,24 @@ class ConfigItem:
return (section_name, key, value)
class SignToolAction(argparse.Action):
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Union[str, Sequence[Any], None] = None,
option_string: Optional[str] = None,
) -> None:
if values is None:
setattr(namespace, 'signtool', None)
elif values == 'sbsign':
setattr(namespace, 'signtool', SbSign)
elif values == 'pesign':
setattr(namespace, 'signtool', PeSign)
else:
raise ValueError(f"Unknon signtool '{values}' (this is unreachable)")
VERBS = ('build', 'genkey', 'inspect')
CONFIG_ITEMS = [
@ -1566,6 +1574,7 @@ CONFIG_ITEMS = [
ConfigItem(
'--signtool',
choices=('sbsign', 'pesign'),
action=SignToolAction,
dest='signtool',
help=(
'whether to use sbsign or pesign. It will also be inferred by the other '
@ -1880,18 +1889,18 @@ def finalize_options(opts: argparse.Namespace) -> None:
)
elif bool(opts.sb_key) and bool(opts.sb_cert):
# both param given, infer sbsign and in case it was given, ensure signtool=sbsign
if opts.signtool and opts.signtool != 'sbsign':
if opts.signtool and opts.signtool != SbSign:
raise ValueError(
f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' # noqa: E501
)
opts.signtool = 'sbsign'
opts.signtool = SbSign
elif bool(opts.sb_cert_name):
# sb_cert_name given, infer pesign and in case it was given, ensure signtool=pesign
if opts.signtool and opts.signtool != 'pesign':
if opts.signtool and opts.signtool != PeSign:
raise ValueError(
f'Cannot provide --signtool={opts.signtool} with --secureboot-certificate-name='
)
opts.signtool = 'pesign'
opts.signtool = PeSign
if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name:
raise ValueError(