]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ukify: support pesign as alternative to sbsign
authorEmanuele Giuseppe Esposito <eesposit@redhat.com>
Thu, 4 May 2023 15:48:47 +0000 (11:48 -0400)
committerEmanuele Giuseppe Esposito <eesposit@redhat.com>
Wed, 10 May 2023 13:18:27 +0000 (09:18 -0400)
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>
man/ukify.xml
src/ukify/ukify.py

index cc711190fa59afae837ce45ca12dae6275041e46..f5a2fcc3e85cee9e56fd49d55399de1c4e9c8b47 100644 (file)
           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>
 
           <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>
index 9db84afdbbb9da3ebfbd22dff6e94e611a66b9ab..d87670eb24aaa6e4c90ec6c3844656b830d85db8 100755 (executable)
@@ -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 make_uki(opts):
-    # kernel payload signing
+def find_sbsign(opts=None):
+    return find_tool('sbsign', opts=opts)
 
-    sbsign_tool = find_tool('sbsign', opts=opts)
-    sbsign_invocation = [
+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:
-        sbsign_invocation += ['--engine', opts.signing_engine]
+        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)
 
-    sign_kernel = opts.sign_kernel
-    if sign_kernel is None and opts.linux is not None and opts.sb_key:
-        # figure out if we should sign the kernel
-        sbverify_tool = find_tool('sbverify', opts=opts)
+SBVERIFY = {
+    'name': 'sbverify',
+    'option': '--list',
+    'output': 'No signature table present',
+}
 
-        cmd = [
-            sbverify_tool,
-            '--list',
-            opts.linux,
-        ]
+PESIGCHECK = {
+    'name': 'pesign',
+    'option': '-i',
+    'output': 'No signatures found.',
+    'flags': '-S'
+}
 
-        print('+', shell_join(cmd))
-        info = subprocess.check_output(cmd, text=True)
+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
+
+    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
+
+    sign_args_present = opts.sb_key or opts.sb_cert_name
 
-        # sbverify has wonderful API
-        if 'No signature table present' in info:
-            sign_kernel = True
+    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 sign_args_present:
+        # figure out if we should sign the kernel
+        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: