ukify: support pesign as alternative to sbsign

sbsign is not available everywhere, for example RHEL does not have it.
Add pesign as alternative to it.

pesign will use options "--secureboot-certificate-name" (mandatory) and
"--secureboot-certificate-dir" (optional), while sbsign will use
"--secureboot-private-key" and "--secureboot-certificate".

By default, use sbsign. If no key/cert is provided or sbsign is not found,
try pesign.

Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
This commit is contained in:
Emanuele Giuseppe Esposito 2023-05-04 11:48:47 -04:00
parent e673c5c2d9
commit c1e8d1727b
2 changed files with 141 additions and 51 deletions

View file

@ -253,13 +253,23 @@
system.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBootSigningTool=<replaceable>SIGNER</replaceable></varname></term>
<term><option>--signtool=<replaceable>SIGNER</replaceable></option></term>
<listitem><para>Whether to use <literal>sbsign</literal> or <literal>pesign</literal>.
Depending on this choice, different parameters are required in order to sign an image.
Defaults to <literal>sbsign</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBootPrivateKey=<replaceable>SB_KEY</replaceable></varname></term>
<term><option>--secureboot-private-key=<replaceable>SB_KEY</replaceable></option></term>
<listitem><para>A path to a private key to use for signing of the resulting binary. If the
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> option is used, this may also be
an engine-specific designation.</para></listitem>
an engine-specific designation. This option is required by
<varname>SecureBootSigningTool=sbsign</varname>/<option>--signtool=sbsign</option>. </para></listitem>
</varlistentry>
<varlistentry>
@ -268,7 +278,25 @@
<listitem><para>A path to a certificate to use for signing of the resulting binary. If the
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> option is used, this may also
be an engine-specific designation.</para></listitem>
be an engine-specific designation. This option is required by
<varname>SecureBootSigningTool=sbsign</varname>/<option>--signtool=sbsign</option>. </para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBootCertificateDir=<replaceable>SB_PATH</replaceable></varname></term>
<term><option>--secureboot-certificate-dir=<replaceable>SB_PATH</replaceable></option></term>
<listitem><para>A path to a nss certificate database directory to use for signing of the resulting binary.
Takes effect when <varname>SecureBootSigningTool=pesign</varname>/<option>--signtool=pesign</option> is used.
Defaults to <filename>/etc/pki/pesign</filename>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBootCertificateName=<replaceable>SB_CERTNAME</replaceable></varname></term>
<term><option>--secureboot-certificate-name=<replaceable>SB_CERTNAME</replaceable></option></term>
<listitem><para>The name of the nss certificate database entry to use for signing of the resulting binary.
This option is required by <varname>SecureBootSigningTool=pesign</varname>/<option>--signtool=pesign</option>.</para></listitem>
</varlistentry>
<varlistentry>

View file

@ -550,50 +550,93 @@ def pe_add_sections(uki: UKI, output: str):
pe.write(output)
def signer_sign(cmd):
print('+', shell_join(cmd))
subprocess.check_call(cmd)
def find_sbsign(opts=None):
return find_tool('sbsign', opts=opts)
def sbsign_sign(sbsign_tool, input_f, output_f, opts=None):
sign_invocation = [
sbsign_tool,
'--key', opts.sb_key,
'--cert', opts.sb_cert,
input_f,
'--output', output_f,
]
if opts.signing_engine is not None:
sign_invocation += ['--engine', opts.signing_engine]
signer_sign(sign_invocation)
def find_pesign(opts=None):
return find_tool('pesign', opts=opts)
def pesign_sign(pesign_tool, input_f, output_f, opts=None):
sign_invocation = [
pesign_tool, '-s', '--force',
'-n', opts.sb_certdir,
'-c', opts.sb_cert_name,
'-i', input_f,
'-o', output_f,
]
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, opts):
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
def make_uki(opts):
# kernel payload signing
sbsign_tool = find_tool('sbsign', opts=opts)
sbsign_invocation = [
sbsign_tool,
'--key', opts.sb_key,
'--cert', opts.sb_cert,
]
sign_tool = None
if opts.signtool == 'sbsign':
sign_tool = find_sbsign(opts=opts)
sign = sbsign_sign
verify_tool = SBVERIFY
else:
sign_tool = find_pesign(opts=opts)
sign = pesign_sign
verify_tool = PESIGCHECK
if opts.signing_engine is not None:
sbsign_invocation += ['--engine', opts.signing_engine]
sign_args_present = opts.sb_key or opts.sb_cert_name
if sign_tool is None and sign_args_present:
raise ValueError(f'{opts.signtool}, required for signing, is not installed')
sign_kernel = opts.sign_kernel
if sign_kernel is None and opts.linux is not None and opts.sb_key:
if sign_kernel is None and opts.linux is not None and sign_args_present:
# figure out if we should sign the kernel
sbverify_tool = find_tool('sbverify', opts=opts)
cmd = [
sbverify_tool,
'--list',
opts.linux,
]
print('+', shell_join(cmd))
info = subprocess.check_output(cmd, text=True)
# sbverify has wonderful API
if 'No signature table present' in info:
sign_kernel = True
sign_kernel = verify(verify_tool, opts)
if sign_kernel:
linux_signed = tempfile.NamedTemporaryFile(prefix='linux-signed')
linux = linux_signed.name
cmd = [
*sbsign_invocation,
opts.linux,
'--output', linux,
]
print('+', shell_join(cmd))
subprocess.check_call(cmd)
sign(sign_tool, opts.linux, linux, opts=opts)
else:
linux = opts.linux
@ -641,7 +684,7 @@ def make_uki(opts):
if linux is not None:
uki.add_section(Section.create('.linux', linux, measure=True))
if opts.sb_key:
if sign_args_present:
unsigned = tempfile.NamedTemporaryFile(prefix='uki')
output = unsigned.name
else:
@ -651,20 +694,14 @@ def make_uki(opts):
# UKI signing
if opts.sb_key:
cmd = [
*sbsign_invocation,
unsigned.name,
'--output', opts.output,
]
print('+', shell_join(cmd))
subprocess.check_call(cmd)
if sign_args_present:
sign(sign_tool, unsigned.name, opts.output, opts=opts)
# We end up with no executable bits, let's reapply them
os.umask(umask := os.umask(0))
os.chmod(opts.output, 0o777 & ~umask)
print(f"Wrote {'signed' if opts.sb_key else 'unsigned'} {opts.output}")
print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}")
@dataclasses.dataclass(frozen=True)
@ -913,18 +950,39 @@ CONFIG_ITEMS = [
help = 'OpenSSL engine to use for signing',
config_key = 'UKI/SigningEngine',
),
ConfigItem(
'--signtool',
choices = ('sbsign', 'pesign'),
dest = 'signtool',
default = 'sbsign',
help = 'whether to use sbsign or pesign. Default is sbsign.',
config_key = 'UKI/SecureBootSigningTool',
),
ConfigItem(
'--secureboot-private-key',
dest = 'sb_key',
help = 'path to key file or engine-specific designation for SB signing',
help = 'required by --signtool=sbsign. Path to key file or engine-specific designation for SB signing',
config_key = 'UKI/SecureBootPrivateKey',
),
ConfigItem(
'--secureboot-certificate',
dest = 'sb_cert',
help = 'path to certificate file or engine-specific designation for SB signing',
help = 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing',
config_key = 'UKI/SecureBootCertificate',
),
ConfigItem(
'--secureboot-certificate-dir',
dest = 'sb_certdir',
default = '/etc/pki/pesign',
help = 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign',
config_key = 'UKI/SecureBootCertificateDir',
),
ConfigItem(
'--secureboot-certificate-name',
dest = 'sb_cert_name',
help = 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing',
config_key = 'UKI/SecureBootCertificateName',
),
ConfigItem(
'--sign-kernel',
@ -1091,16 +1149,20 @@ def finalize_options(opts):
if opts.sb_cert:
opts.sb_cert = pathlib.Path(opts.sb_cert)
if bool(opts.sb_key) ^ bool(opts.sb_cert):
raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together')
if opts.signtool == 'sbsign':
if bool(opts.sb_key) ^ bool(opts.sb_cert):
raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together when using --signtool=sbsign')
else:
if not bool(opts.sb_cert_name):
raise ValueError('--certificate-name must be specified when using --signtool=pesign')
if opts.sign_kernel and not opts.sb_key:
raise ValueError('--sign-kernel requires --secureboot-private-key= and --secureboot-certificate= to be specified')
if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name:
raise ValueError('--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified')
if opts.output is None:
if opts.linux is None:
raise ValueError('--output= must be specified when building a PE addon')
suffix = '.efi' if opts.sb_key else '.unsigned.efi'
suffix = '.efi' if opts.sb_key or opts.sb_cert_name else '.unsigned.efi'
opts.output = opts.linux.name + suffix
for section in opts.sections: