mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
Introduce systemd-sbsign to do secure boot signing (#35021)
Currently in mkosi and ukify we use sbsigntools to do secure boot signing. This has multiple issues: - sbsigntools is practically unmaintained, sbvarsign is completely broken with the latest gnu-efi when built without -fshort-wchar and upstream has completely ignored my bug report about this. - sbsigntools only supports openssl engines and not the new providers API. - sbsigntools doesn't allow us to cache hardware token pins in the kernel keyring like we do nowadays when we sign stuff ourselves in systemd-repart or systemd-measure There are alternative tools like sbctl and pesign but these do not support caching hardware token pins in the kernel keyring either. To get around the issues with sbsigntools, let's introduce our own tool systemd-sbsign to do secure boot signing. This allows us to take advantage of our own openssl infra so that hardware token pins are cached in the kernel keyring as expected and we get openssl provider support as well.
This commit is contained in:
commit
e5011dd239
111
man/systemd-sbsign.xml
Normal file
111
man/systemd-sbsign.xml
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version='1.0'?> <!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
|
||||
<refentry id="systemd-sbsign"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<refentryinfo>
|
||||
<title>systemd-sbsign</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-sbsign</refentrytitle>
|
||||
<manvolnum>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-sbsign</refname>
|
||||
<refpurpose>Sign PE binaries for EFI Secure Boot</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-sbsign</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="req">COMMAND</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><command>systemd-sbsign</command> can be used to sign PE binaries for EFI Secure Boot.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Commands</title>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>sign</option></term>
|
||||
|
||||
<listitem><para>Signs the given PE binary for EFI Secure Boot. Takes a path to a PE binary as its
|
||||
argument. If the PE binary already has a certificate table, the new signature will be added to it.
|
||||
Otherwise a new certificate table will be created. The signed PE binary will be written to the path
|
||||
specified with <option>--output=</option>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>validate-key</option></term>
|
||||
|
||||
<listitem><para>Checks that we can load the private key specified with
|
||||
<option>--private-key=</option>. </para>
|
||||
|
||||
<para>As a side effect, if the private key is loaded from a PIN-protected hardware token, this
|
||||
command can be used to cache the PIN in the kernel keyring. The
|
||||
<varname>$SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC</varname> and
|
||||
<varname>$SYSTEMD_ASK_PASSWORD_KEYRING_TYPE</varname> environment variables can be used to control
|
||||
how long and in which kernel keyring the PIN is cached.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--output=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Specifies the path where to write the signed PE binary.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--private-key=<replaceable>PATH/URI</replaceable></option></term>
|
||||
<term><option>--private-key-source=<replaceable>TYPE</replaceable>[:<replaceable>NAME<replaceable>]</option></term>
|
||||
<term><option>--certificate=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Set the Secure Boot private key and certificate for use with the
|
||||
<command>sign</command>. The <option>--certificate=</option> option takes a path to a PEM encoded
|
||||
X.509 certificate. The <option>--private-key=</option> option can take a path or a URI that will be
|
||||
passed to the OpenSSL engine or provider, as specified by <option>--private-key-source=</option> as a
|
||||
<literal>type:name</literal> tuple, such as <literal>engine:pkcs11</literal>. The specified OpenSSL
|
||||
signing engine or provider will be used to sign the PE binary.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager"/>
|
||||
<xi:include href="standard-options.xml" xpointer="help"/>
|
||||
<xi:include href="standard-options.xml" xpointer="version"/>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
</refentry>
|
@ -100,10 +100,12 @@
|
||||
the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust
|
||||
policies for different phases of the boot. In the config file, <varname>PCRPrivateKey=</varname>,
|
||||
<varname>PCRPublicKey=</varname>, and <varname>Phases=</varname> are grouped into separate sections,
|
||||
describing separate boot phases. If <varname>SigningEngine=</varname>/<option>--signing-engine=</option>
|
||||
is specified, then the private keys arguments will be passed verbatim to OpenSSL as URIs, and the public
|
||||
key arguments will be loaded as X.509 certificates, so that signing can be performed with an OpenSSL
|
||||
engine.</para>
|
||||
describing separate boot phases. If one of
|
||||
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> or
|
||||
<varname>SigningProvider=</varname>/<option>--signing-provider=</option> is specified, then the private
|
||||
key arguments will be passed verbatim to OpenSSL as URIs, and the public key arguments will be loaded
|
||||
as X.509 certificates, so that signing can be performed with an OpenSSL engine or provider
|
||||
respectively.</para>
|
||||
|
||||
<para>If a SecureBoot signing key is provided via the
|
||||
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> option, the resulting
|
||||
@ -440,9 +442,9 @@
|
||||
<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><para>Whether to use <literal>sbsign</literal>, <literal>pesign</literal>, or
|
||||
<literal>systemd-sbsign</literal>. Depending on this choice, different parameters are required in
|
||||
order to sign an image. Defaults to <literal>sbsign</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
|
||||
</varlistentry>
|
||||
@ -452,8 +454,9 @@
|
||||
<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. This option is required by
|
||||
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> or
|
||||
<varname>SigningProvider=</varname>/<option>--signing-provider=</option> option is used, this may
|
||||
also be an engine or provider specific designation. This option is required by
|
||||
<varname>SecureBootSigningTool=sbsign</varname>/<option>--signtool=sbsign</option>. </para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
@ -464,8 +467,9 @@
|
||||
<term><option>--secureboot-certificate=<replaceable>SB_CERT</replaceable></option></term>
|
||||
|
||||
<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. This option is required by
|
||||
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> or
|
||||
<varname>SigningProvider=</varname>/<option>--signing-provider=</option> option is used, this may
|
||||
also be an engine or provider specific designation. This option is required by
|
||||
<varname>SecureBootSigningTool=sbsign</varname>/<option>--signtool=sbsign</option>. </para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
@ -506,14 +510,23 @@
|
||||
<term><varname>SigningEngine=<replaceable>ENGINE</replaceable></varname></term>
|
||||
<term><option>--signing-engine=<replaceable>ENGINE</replaceable></option></term>
|
||||
|
||||
<listitem><para>An "engine" for signing of the resulting binary. This option is currently passed
|
||||
verbatim to the <option>--engine=</option> option of
|
||||
<citerefentry project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||
<listitem><para>An OpenSSL engine to be used for signing the resulting binary and PCR measurements.
|
||||
</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SigningProvider=<replaceable>PROVIDER</replaceable></varname></term>
|
||||
<term><option>--signing-provider=<replaceable>PROVIDER</replaceable></option></term>
|
||||
|
||||
<listitem><para>An OpenSSL provider to be used for signing the resulting binary and PCR
|
||||
measurements. This option can only be used when using <command>systemd-sbsign</command> as the
|
||||
signing tool.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SignKernel=<replaceable>BOOL</replaceable></varname></term>
|
||||
<term><option>--sign-kernel</option></term>
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
[Build]
|
||||
ToolsTreePackages=
|
||||
meson
|
||||
gcc
|
||||
gperf
|
||||
meson
|
||||
mypy
|
||||
pkgconf
|
||||
ruff
|
||||
|
135
src/boot/authenticode.h
Normal file
135
src/boot/authenticode.h
Normal file
@ -0,0 +1,135 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <openssl/asn1t.h>
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
#define SPC_INDIRECT_DATA_OBJID "1.3.6.1.4.1.311.2.1.4"
|
||||
#define SPC_PE_IMAGE_DATA_OBJID "1.3.6.1.4.1.311.2.1.15"
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *type;
|
||||
ASN1_TYPE *value;
|
||||
} SpcAttributeTypeAndOptionalValue;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
|
||||
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
|
||||
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *algorithm;
|
||||
ASN1_TYPE *parameters;
|
||||
} AlgorithmIdentifier;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
ASN1_SEQUENCE(AlgorithmIdentifier) = {
|
||||
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
|
||||
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(AlgorithmIdentifier)
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
typedef struct {
|
||||
AlgorithmIdentifier *digestAlgorithm;
|
||||
ASN1_OCTET_STRING *digest;
|
||||
} DigestInfo;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
ASN1_SEQUENCE(DigestInfo) = {
|
||||
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
|
||||
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
|
||||
} ASN1_SEQUENCE_END(DigestInfo);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
typedef struct {
|
||||
SpcAttributeTypeAndOptionalValue *data;
|
||||
DigestInfo *messageDigest;
|
||||
} SpcIndirectDataContent;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
ASN1_SEQUENCE(SpcIndirectDataContent) = {
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
|
||||
} ASN1_SEQUENCE_END(SpcIndirectDataContent);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_BMPSTRING *unicode;
|
||||
ASN1_IA5STRING *ascii;
|
||||
} value;
|
||||
} SpcString;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcString);
|
||||
|
||||
ASN1_CHOICE(SpcString) = {
|
||||
ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
|
||||
ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
|
||||
} ASN1_CHOICE_END(SpcString);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcString);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OCTET_STRING *classId;
|
||||
ASN1_OCTET_STRING *serializedData;
|
||||
} SpcSerializedObject;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcSerializedObject);
|
||||
|
||||
ASN1_SEQUENCE(SpcSerializedObject) = {
|
||||
ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
|
||||
ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
|
||||
} ASN1_SEQUENCE_END(SpcSerializedObject);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_IA5STRING *url;
|
||||
SpcSerializedObject *moniker;
|
||||
SpcString *file;
|
||||
} value;
|
||||
} SpcLink;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcLink);
|
||||
|
||||
ASN1_CHOICE(SpcLink) = {
|
||||
ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0),
|
||||
ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1),
|
||||
ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2)
|
||||
} ASN1_CHOICE_END(SpcLink);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcLink);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
|
||||
|
||||
typedef struct {
|
||||
ASN1_BIT_STRING *flags;
|
||||
SpcLink *file;
|
||||
} SpcPeImageData;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
ASN1_SEQUENCE(SpcPeImageData) = {
|
||||
ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
|
||||
ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
|
||||
} ASN1_SEQUENCE_END(SpcPeImageData)
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
|
@ -839,7 +839,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
/* When signing we only support JSON output */
|
||||
arg_json_format_flags &= ~SD_JSON_FORMAT_OFF;
|
||||
|
||||
/* This must be done before openssl_load_key_from_token() otherwise it will get stuck */
|
||||
/* This must be done before openssl_load_private_key() otherwise it will get stuck */
|
||||
if (arg_certificate) {
|
||||
r = openssl_load_x509_certificate(arg_certificate, &certificate);
|
||||
if (r < 0)
|
||||
|
@ -62,6 +62,14 @@ executables += [
|
||||
'sources' : files('measure.c'),
|
||||
'dependencies' : libopenssl,
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-sbsign',
|
||||
'conditions' : [
|
||||
'HAVE_OPENSSL',
|
||||
],
|
||||
'sources' : files('sbsign.c'),
|
||||
'dependencies' : libopenssl,
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-boot-check-no-failures',
|
||||
'sources' : files('boot-check-no-failures.c'),
|
||||
|
523
src/boot/sbsign.c
Normal file
523
src/boot/sbsign.c
Normal file
@ -0,0 +1,523 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "ansi-color.h"
|
||||
#include "authenticode.h"
|
||||
#include "build.h"
|
||||
#include "copy.h"
|
||||
#include "efi-fundamental.h"
|
||||
#include "fd-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pe-binary.h"
|
||||
#include "pretty-print.h"
|
||||
#include "stat-util.h"
|
||||
#include "tmpfile-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static char *arg_output = NULL;
|
||||
static char *arg_certificate = NULL;
|
||||
static char *arg_private_key = NULL;
|
||||
static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
|
||||
static char *arg_private_key_source = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-sbsign", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sSign binaries for EFI Secure Boot%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" sign EXEFILE Sign the given binary for EFI Secure Boot\n"
|
||||
" validate-key Load and validate the given private key\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Print version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --output Where to write the signed PE binary\n"
|
||||
" --certificate=PATH PEM certificate to use when signing with a URI\n"
|
||||
" --private-key=KEY Private key (PEM) to sign with\n"
|
||||
" --private-key-source=file|provider:PROVIDER|engine:ENGINE\n"
|
||||
" Specify how to use KEY for --private-key=. Allows\n"
|
||||
" an OpenSSL engine/provider to be used for signing\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_OUTPUT,
|
||||
ARG_CERTIFICATE,
|
||||
ARG_PRIVATE_KEY,
|
||||
ARG_PRIVATE_KEY_SOURCE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "output", required_argument, NULL, ARG_OUTPUT },
|
||||
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
|
||||
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
|
||||
{ "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
help(0, NULL, NULL);
|
||||
return 0;
|
||||
|
||||
case ARG_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_OUTPUT:
|
||||
r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_output);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_CERTIFICATE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_PRIVATE_KEY:
|
||||
r = free_and_strdup_warn(&arg_private_key, optarg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_PRIVATE_KEY_SOURCE:
|
||||
r = parse_openssl_key_source_argument(
|
||||
optarg,
|
||||
&arg_private_key_source,
|
||||
&arg_private_key_source_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (arg_private_key_source && !arg_certificate)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int verb_sign(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
|
||||
_cleanup_(X509_freep) X509 *certificate = NULL;
|
||||
int r;
|
||||
|
||||
if (argc < 2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified");
|
||||
|
||||
if (!arg_certificate)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No certificate specified, use --certificate=");
|
||||
|
||||
if (!arg_private_key)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No private key specified, use --private-key=.");
|
||||
|
||||
if (!arg_output)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
|
||||
|
||||
r = openssl_load_x509_certificate(arg_certificate, &certificate);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
|
||||
|
||||
if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
|
||||
}
|
||||
|
||||
r = openssl_load_private_key(
|
||||
arg_private_key_source_type,
|
||||
arg_private_key_source,
|
||||
arg_private_key,
|
||||
&(AskPasswordRequest) {
|
||||
.id = "sbsign-private-key-pin",
|
||||
.keyring = arg_private_key,
|
||||
.credential = "sbsign.private-key-pin",
|
||||
},
|
||||
&private_key,
|
||||
&ui);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
p7 = PKCS7_sign(certificate, private_key, /*certs=*/ NULL, /*data=*/ NULL, PKCS7_BINARY|PKCS7_PARTIAL);
|
||||
if (!p7)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate pkcs7 signing context: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
STACK_OF(PKCS7_SIGNER_INFO) *si_stack = PKCS7_get_signer_info(p7);
|
||||
if (!si_stack)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info stack: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(si_stack, 0);
|
||||
if (!si)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get pkcs7 signer info: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
int idcnid = OBJ_create(SPC_INDIRECT_DATA_OBJID, "spcIndirectDataContext", "Indirect Data Context");
|
||||
|
||||
if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, OBJ_nid2obj(idcnid)) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signed attribute to pkcs7 signer info: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_close_ int srcfd = open(argv[1], O_RDONLY|O_CLOEXEC);
|
||||
if (srcfd < 0)
|
||||
return log_error_errno(errno, "Failed to open %s: %m", argv[1]);
|
||||
|
||||
struct stat st;
|
||||
if (fstat(srcfd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat %s: %m", argv[1]);
|
||||
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "%s is not a regular file: %m", argv[1]);
|
||||
|
||||
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
||||
_cleanup_close_ int dstfd = open_tmpfile_linkable(arg_output, O_RDWR|O_CLOEXEC, &tmp);
|
||||
if (dstfd < 0)
|
||||
return log_error_errno(r, "Failed to open temporary file: %m");
|
||||
|
||||
r = copy_bytes(srcfd, dstfd, UINT64_MAX, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy %s to %s: %m", argv[1], tmp);
|
||||
|
||||
_cleanup_free_ void *hash = NULL;
|
||||
size_t hashsz;
|
||||
r = pe_hash(dstfd, EVP_sha256(), &hash, &hashsz);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
|
||||
|
||||
/* <<<Obsolete>>> in unicode bytes. */
|
||||
static const uint8_t obsolete[] = {
|
||||
0x00, 0x3c, 0x00, 0x3c, 0x00, 0x3c, 0x00, 0x4f,
|
||||
0x00, 0x62, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x6c,
|
||||
0x00, 0x65, 0x00, 0x74, 0x00, 0x65, 0x00, 0x3e,
|
||||
0x00, 0x3e, 0x00, 0x3e
|
||||
};
|
||||
|
||||
_cleanup_(SpcLink_freep) SpcLink *link = SpcLink_new();
|
||||
if (!link)
|
||||
return log_oom();
|
||||
|
||||
link->type = 2;
|
||||
link->value.file = SpcString_new();
|
||||
if (!link->value.file)
|
||||
return log_oom();
|
||||
|
||||
link->value.file->type = 0;
|
||||
link->value.file->value.unicode = ASN1_BMPSTRING_new();
|
||||
if (!link->value.file->value.unicode)
|
||||
return log_oom();
|
||||
|
||||
if (ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
|
||||
if (!peid)
|
||||
return log_oom();
|
||||
|
||||
if (ASN1_BIT_STRING_set_bit(peid->flags, 0, 1) == 0)
|
||||
return log_oom();
|
||||
|
||||
peid->file = TAKE_PTR(link);
|
||||
|
||||
_cleanup_free_ uint8_t *peidraw = NULL;
|
||||
int peidrawsz = i2d_SpcPeImageData(peid, &peidraw);
|
||||
if (peidrawsz < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcPeImageData to BER: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
|
||||
idc->data->value = ASN1_TYPE_new();
|
||||
if (!idc->data->value)
|
||||
return log_oom();
|
||||
|
||||
idc->data->value->type = V_ASN1_SEQUENCE;
|
||||
idc->data->value->value.sequence = ASN1_STRING_new();
|
||||
if (!idc->data->value->value.sequence)
|
||||
return log_oom();
|
||||
|
||||
idc->data->type = OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /*no_name=*/ 1);
|
||||
if (!idc->data->type)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcPeImageData object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
idc->data->value->value.sequence->data = TAKE_PTR(peidraw);
|
||||
idc->data->value->value.sequence->length = peidrawsz;
|
||||
idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256);
|
||||
if (!idc->messageDigest->digestAlgorithm->algorithm)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SHA256 object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters = ASN1_TYPE_new();
|
||||
if (!idc->messageDigest->digestAlgorithm->parameters)
|
||||
return log_oom();
|
||||
|
||||
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
|
||||
|
||||
if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashsz) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_free_ uint8_t *idcraw = NULL;
|
||||
int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw);
|
||||
if (idcrawsz < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_(BIO_free_allp) BIO *bio = PKCS7_dataInit(p7, NULL);
|
||||
if (!bio)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to create PKCS7 data bio: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
int tag, class;
|
||||
long psz;
|
||||
const uint8_t *p = idcraw;
|
||||
|
||||
/* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */
|
||||
if (ASN1_get_object(&p, &psz, &tag, &class, idcrawsz) & 0x80)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (BIO_write(bio, p, psz) < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write to PKCS7 data bio: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (PKCS7_final(p7, bio, PKCS7_BINARY) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7c = PKCS7_new();
|
||||
if (!p7c)
|
||||
return log_oom();
|
||||
|
||||
p7c->type = OBJ_nid2obj(idcnid);
|
||||
if (!p7c->type)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
p7c->d.other = ASN1_TYPE_new();
|
||||
if (!p7c->d.other)
|
||||
return log_oom();
|
||||
|
||||
p7c->d.other->type = V_ASN1_SEQUENCE;
|
||||
p7c->d.other->value.sequence = ASN1_STRING_new();
|
||||
if (!p7c->d.other->value.sequence)
|
||||
return log_oom();
|
||||
|
||||
if (ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (PKCS7_set_content(p7, p7c) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 data: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
TAKE_PTR(p7c);
|
||||
|
||||
_cleanup_free_ uint8_t *sig = NULL;
|
||||
int sigsz = i2d_PKCS7(p7, &sig);
|
||||
if (sigsz < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
r = pe_load_headers(srcfd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load headers from PE file: %m");
|
||||
|
||||
const IMAGE_DATA_DIRECTORY *certificate_table;
|
||||
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
|
||||
if (!certificate_table)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
|
||||
|
||||
off_t end = st.st_size;
|
||||
ssize_t n;
|
||||
|
||||
if (st.st_size % 8 != 0) {
|
||||
if (certificate_table->VirtualAddress != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Certificate table is not aligned to 8 bytes");
|
||||
|
||||
n = pwrite(dstfd, (const uint8_t[8]) {}, 8 - (st.st_size % 8), st.st_size);
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to write zero padding: %m");
|
||||
if (n != 8 - (st.st_size % 8))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing zero padding.");
|
||||
|
||||
end += n;
|
||||
}
|
||||
|
||||
uint32_t certsz = offsetof(WIN_CERTIFICATE, bCertificate) + sigsz;
|
||||
n = pwrite(dstfd,
|
||||
&(WIN_CERTIFICATE) {
|
||||
.wRevision = htole16(0x200),
|
||||
.wCertificateType = htole16(0x0002), /* PKCS7 signedData */
|
||||
.dwLength = htole32(ROUND_UP(certsz, 8)),
|
||||
},
|
||||
sizeof(WIN_CERTIFICATE),
|
||||
end);
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to write certificate header: %m");
|
||||
if (n != sizeof(WIN_CERTIFICATE))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing certificate header.");
|
||||
|
||||
end += n;
|
||||
|
||||
n = pwrite(dstfd, sig, sigsz, end);
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to write signature: %m");
|
||||
if (n != sigsz)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing signature.");
|
||||
|
||||
end += n;
|
||||
|
||||
if (certsz % 8 != 0) {
|
||||
n = pwrite(dstfd, (const uint8_t[8]) {}, 8 - (certsz % 8), end);
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to write zero padding: %m");
|
||||
if ((size_t) n != 8 - (certsz % 8))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing zero padding.");
|
||||
}
|
||||
|
||||
n = pwrite(dstfd,
|
||||
&(IMAGE_DATA_DIRECTORY) {
|
||||
.VirtualAddress = certificate_table->VirtualAddress ?: htole32(ROUND_UP(st.st_size, 8)),
|
||||
.Size = htole32(le32toh(certificate_table->Size) + ROUND_UP(certsz, 8)),
|
||||
},
|
||||
sizeof(IMAGE_DATA_DIRECTORY),
|
||||
le32toh(dos_header->e_lfanew) + PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]));
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to update PE certificate table: %m");
|
||||
if ((size_t) n != sizeof(IMAGE_DATA_DIRECTORY))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while updating PE certificate table.");
|
||||
|
||||
uint32_t checksum;
|
||||
r = pe_checksum(dstfd, &checksum);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to calculate PE file checksum: %m");
|
||||
|
||||
n = pwrite(dstfd,
|
||||
&(le32_t) { htole32(checksum) },
|
||||
sizeof(le32_t),
|
||||
le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum));
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to update PE checksum: %m");
|
||||
if ((size_t) n != sizeof(le32_t))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while updating PE checksum.");
|
||||
|
||||
r = link_tmpfile(dstfd, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to link temporary file to %s: %m", arg_output);
|
||||
|
||||
log_info("Wrote signed PE binary to %s", arg_output);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_validate_key(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
|
||||
int r;
|
||||
|
||||
if (!arg_private_key)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No private key specified, use --private-key=.");
|
||||
|
||||
if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) {
|
||||
r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key);
|
||||
}
|
||||
|
||||
r = openssl_load_private_key(
|
||||
arg_private_key_source_type,
|
||||
arg_private_key_source,
|
||||
arg_private_key,
|
||||
&(AskPasswordRequest) {
|
||||
.id = "sbsign-private-key-pin",
|
||||
.keyring = arg_private_key,
|
||||
.credential = "sbsign.private-key-pin",
|
||||
},
|
||||
&private_key,
|
||||
&ui);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
|
||||
|
||||
puts("OK");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, help },
|
||||
{ "sign", 2, 2, 0, verb_sign },
|
||||
{ "validate-key", VERB_ANY, 1, 0, verb_validate_key },
|
||||
{}
|
||||
};
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
@ -10,7 +10,6 @@ executables += [
|
||||
'sources' : files(
|
||||
'pcrlock.c',
|
||||
'pcrlock-firmware.c',
|
||||
'pehash.c',
|
||||
),
|
||||
'dependencies' : [
|
||||
libm,
|
||||
|
@ -40,7 +40,7 @@
|
||||
#include "path-util.h"
|
||||
#include "pcrextend-util.h"
|
||||
#include "pcrlock-firmware.h"
|
||||
#include "pehash.h"
|
||||
#include "pe-binary.h"
|
||||
#include "pretty-print.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "random-util.h"
|
||||
|
@ -1,264 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "pe-binary.h"
|
||||
#include "pehash.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
|
||||
/* Implements:
|
||||
*
|
||||
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
|
||||
* → Section "Calculating the PE Image Hash"
|
||||
*/
|
||||
|
||||
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
|
||||
|
||||
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
|
||||
uint8_t buffer[64*1024];
|
||||
|
||||
log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
|
||||
|
||||
while (size > 0) {
|
||||
size_t m = MIN(size, sizeof(buffer));
|
||||
ssize_t n;
|
||||
|
||||
n = pread(fd, buffer, m, offset);
|
||||
if (n < 0)
|
||||
return log_debug_errno(errno, "Failed to read file for hashing: %m");
|
||||
if ((size_t) n != m)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
|
||||
|
||||
if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
offset += m;
|
||||
size -= m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
|
||||
return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
|
||||
}
|
||||
|
||||
int pe_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void **ret_hash,
|
||||
size_t *ret_hash_size) {
|
||||
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
const IMAGE_DATA_DIRECTORY *certificate_table;
|
||||
struct stat st;
|
||||
uint64_t p, q;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(md);
|
||||
assert(ret_hash_size);
|
||||
assert(ret_hash);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat file: %m");
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Not a regular file: %m");
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
|
||||
if (!certificate_table)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
/* Everything from beginning of file to CheckSum field in PE header */
|
||||
p = (uint64_t) dos_header->e_lfanew +
|
||||
offsetof(PeHeader, optional.CheckSum);
|
||||
r = hash_file(fd, mdctx, 0, p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
p += sizeof(le32_t);
|
||||
|
||||
/* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
|
||||
q = (uint64_t) dos_header->e_lfanew +
|
||||
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
|
||||
r = hash_file(fd, mdctx, p, q - p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
q += sizeof(IMAGE_DATA_DIRECTORY);
|
||||
|
||||
/* The rest of the header + the section table */
|
||||
p = pe_header->optional.SizeOfHeaders;
|
||||
if (p < q)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
|
||||
r = hash_file(fd, mdctx, q, p - q);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Sort by location in file */
|
||||
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p += section->SizeOfRawData;
|
||||
}
|
||||
|
||||
if ((uint64_t) st.st_size > p) {
|
||||
|
||||
if (st.st_size - p < certificate_table->Size)
|
||||
return log_debug_errno(errno, "No space for certificate table, refusing.");
|
||||
|
||||
r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
|
||||
if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
}
|
||||
|
||||
int hsz = EVP_MD_CTX_size(mdctx);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
_cleanup_free_ void *hash = malloc(hsz);
|
||||
if (!hash)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
*ret_hash = TAKE_PTR(hash);
|
||||
*ret_hash_size = hash_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
|
||||
|
||||
static void section_hash_array_done(SectionHashArray *array) {
|
||||
assert(array);
|
||||
|
||||
for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
|
||||
free((*array)[i]);
|
||||
}
|
||||
|
||||
int uki_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void* ret_hashes[static _UNIFIED_SECTION_MAX],
|
||||
size_t *ret_hash_size) {
|
||||
|
||||
_cleanup_(section_hash_array_done) SectionHashArray hashes = {};
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret_hashes);
|
||||
assert(ret_hash_size);
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
int hsz = EVP_MD_size(md);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
ssize_t i;
|
||||
|
||||
n = memdup_suffix0(section->Name, sizeof(section->Name));
|
||||
if (!n)
|
||||
return log_oom_debug();
|
||||
|
||||
i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
|
||||
if (i < 0)
|
||||
continue;
|
||||
|
||||
if (hashes[i])
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (section->SizeOfRawData < section->VirtualSize) {
|
||||
uint8_t zeroes[1024] = {};
|
||||
size_t remaining = section->VirtualSize - section->SizeOfRawData;
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t sz = MIN(sizeof(zeroes), remaining);
|
||||
|
||||
if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
remaining -= sz;
|
||||
}
|
||||
}
|
||||
|
||||
hashes[i] = malloc(hsz);
|
||||
if (!hashes[i])
|
||||
return log_oom_debug();
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *hs = NULL;
|
||||
|
||||
hs = hexmem(hashes[i], hsz);
|
||||
log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(ret_hashes, hashes, sizeof(hashes));
|
||||
zero(hashes);
|
||||
*ret_hash_size = (unsigned) hsz;
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "openssl-util.h"
|
||||
#include "uki.h"
|
||||
|
||||
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
|
||||
|
||||
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
|
@ -24,6 +24,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL);
|
||||
REENABLE_WARNING;
|
||||
# endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
|
||||
|
||||
/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error
|
||||
* string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL
|
||||
* errors." This logs at level debug. Returns -EIO (or -ENOMEM). */
|
||||
@ -1315,12 +1317,10 @@ int pkey_generate_volume_keys(
|
||||
static int load_key_from_provider(
|
||||
const char *provider,
|
||||
const char *private_key_uri,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret) {
|
||||
|
||||
assert(provider);
|
||||
assert(private_key_uri);
|
||||
assert(ui);
|
||||
assert(ret);
|
||||
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
@ -1333,8 +1333,8 @@ static int load_key_from_provider(
|
||||
|
||||
_cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open(
|
||||
private_key_uri,
|
||||
ui->method,
|
||||
&ui->request,
|
||||
/*ui_method=*/ NULL,
|
||||
/*ui_method=*/ NULL,
|
||||
/* post_process= */ NULL,
|
||||
/* post_process_data= */ NULL);
|
||||
if (!store)
|
||||
@ -1356,10 +1356,9 @@ static int load_key_from_provider(
|
||||
#endif
|
||||
}
|
||||
|
||||
static int load_key_from_engine(const char *engine, const char *private_key_uri, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret) {
|
||||
static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) {
|
||||
assert(engine);
|
||||
assert(private_key_uri);
|
||||
assert(ui);
|
||||
assert(ret);
|
||||
|
||||
#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
|
||||
@ -1371,13 +1370,7 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
|
||||
if (ENGINE_init(e) == 0)
|
||||
return log_openssl_errors("Failed to initialize signing engine '%s'", engine);
|
||||
|
||||
if (ENGINE_ctrl(e, ENGINE_CTRL_SET_USER_INTERFACE, /*i=*/ 0, ui->method, /*f=*/ NULL) <= 0)
|
||||
return log_openssl_errors("Failed to set engine user interface");
|
||||
|
||||
if (ENGINE_ctrl(e, ENGINE_CTRL_SET_CALLBACK_DATA, /*i=*/ 0, &ui->request, /*f=*/ NULL) <= 0)
|
||||
return log_openssl_errors("Failed to set engine user interface data");
|
||||
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui->method, &ui->request);
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, /*ui_method=*/ NULL, /*callback_data=*/ NULL);
|
||||
if (!private_key)
|
||||
return log_openssl_errors("Failed to load private key from '%s'", private_key_uri);
|
||||
REENABLE_WARNING;
|
||||
@ -1390,36 +1383,13 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri,
|
||||
#endif
|
||||
}
|
||||
|
||||
int openssl_load_key_from_token(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
const char *private_key,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret_private_key) {
|
||||
|
||||
assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER));
|
||||
assert(private_key_source);
|
||||
assert(ui);
|
||||
assert(private_key);
|
||||
|
||||
switch (private_key_source_type) {
|
||||
|
||||
case OPENSSL_KEY_SOURCE_ENGINE:
|
||||
return load_key_from_engine(private_key_source, private_key, ui, ret_private_key);
|
||||
case OPENSSL_KEY_SOURCE_PROVIDER:
|
||||
return load_key_from_provider(private_key_source, private_key, ui, ret_private_key);
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
|
||||
int r;
|
||||
|
||||
switch(UI_get_string_type(uis)) {
|
||||
case UIT_PROMPT: {
|
||||
/* If no ask password request was configured use the default openssl UI. */
|
||||
AskPasswordRequest *req = UI_get0_user_data(ui);
|
||||
AskPasswordRequest *req = (AskPasswordRequest*) UI_method_get_ex_data(UI_get_method(ui), 0);
|
||||
if (!req)
|
||||
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
||||
|
||||
@ -1448,10 +1418,41 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) {
|
||||
return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
|
||||
_cleanup_(erase_and_freep) char *rawkey = NULL;
|
||||
_cleanup_(BIO_freep) BIO *kb = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
||||
size_t rawkeysz;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&rawkey, &rawkeysz);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read key file '%s': %m", path);
|
||||
|
||||
kb = BIO_new_mem_buf(rawkey, rawkeysz);
|
||||
if (!kb)
|
||||
return log_oom_debug();
|
||||
|
||||
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
||||
if (!pk)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(pk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) {
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password");
|
||||
@ -1467,12 +1468,31 @@ int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) {
|
||||
|
||||
*ui = (OpenSSLAskPasswordUI) {
|
||||
.method = TAKE_PTR(method),
|
||||
.request = *request,
|
||||
};
|
||||
|
||||
UI_set_default_method(ui->method);
|
||||
|
||||
if (UI_method_set_ex_data(ui->method, 0, &ui->request) == 0)
|
||||
return log_openssl_errors("Failed to set extra data for UI method");
|
||||
|
||||
*ret = TAKE_PTR(ui);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
|
||||
#if HAVE_OPENSSL
|
||||
if (!ui)
|
||||
return NULL;
|
||||
|
||||
assert(UI_get_default_method() == ui->method);
|
||||
UI_set_default_method(UI_OpenSSL());
|
||||
UI_destroy_method(ui->method);
|
||||
return mfree(ui);
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot create ask-password user interface.");
|
||||
assert(ui == NULL);
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1531,43 +1551,6 @@ int openssl_load_x509_certificate(const char *path, X509 **ret) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(erase_and_freep) char *rawkey = NULL;
|
||||
_cleanup_(BIO_freep) BIO *kb = NULL;
|
||||
_cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
|
||||
size_t rawkeysz;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, path, UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
|
||||
NULL,
|
||||
&rawkey, &rawkeysz);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read key file '%s': %m", path);
|
||||
|
||||
kb = BIO_new_mem_buf(rawkey, rawkeysz);
|
||||
if (!kb)
|
||||
return log_oom_debug();
|
||||
|
||||
pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL);
|
||||
if (!pk)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
|
||||
if (ret)
|
||||
*ret = TAKE_PTR(pk);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int openssl_load_private_key(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
@ -1575,7 +1558,7 @@ int openssl_load_private_key(
|
||||
const AskPasswordRequest *request,
|
||||
EVP_PKEY **ret_private_key,
|
||||
OpenSSLAskPasswordUI **ret_user_interface) {
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
int r;
|
||||
|
||||
assert(private_key);
|
||||
@ -1589,18 +1572,21 @@ int openssl_load_private_key(
|
||||
*ret_user_interface = NULL;
|
||||
} else {
|
||||
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
|
||||
r = openssl_ask_password_ui_new(&ui);
|
||||
r = openssl_ask_password_ui_new(request, &ui);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to allocate ask-password user interface: %m");
|
||||
|
||||
ui->request = *request;
|
||||
switch (private_key_source_type) {
|
||||
|
||||
r = openssl_load_key_from_token(
|
||||
private_key_source_type,
|
||||
private_key_source,
|
||||
private_key,
|
||||
ui,
|
||||
ret_private_key);
|
||||
case OPENSSL_KEY_SOURCE_ENGINE:
|
||||
r = load_key_from_engine(private_key_source, private_key, ret_private_key);
|
||||
break;
|
||||
case OPENSSL_KEY_SOURCE_PROVIDER:
|
||||
r = load_key_from_provider(private_key_source, private_key, ret_private_key);
|
||||
break;
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(
|
||||
r,
|
||||
@ -1612,6 +1598,9 @@ int openssl_load_private_key(
|
||||
}
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int parse_openssl_key_source_argument(
|
||||
|
@ -55,6 +55,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL);
|
||||
|
||||
@ -134,59 +135,48 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
|
||||
|
||||
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
|
||||
|
||||
int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret_private_key);
|
||||
|
||||
#else
|
||||
|
||||
typedef struct X509 X509;
|
||||
typedef struct EVP_PKEY EVP_PKEY;
|
||||
typedef struct EVP_MD EVP_MD;
|
||||
typedef struct UI_METHOD UI_METHOD;
|
||||
typedef struct ASN1_TYPE ASN1_TYPE;
|
||||
typedef struct ASN1_STRING ASN1_STRING;
|
||||
|
||||
static inline void *X509_free(X509 *p) {
|
||||
static inline void* X509_free(X509 *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void *EVP_PKEY_free(EVP_PKEY *p) {
|
||||
static inline void* EVP_PKEY_free(EVP_PKEY *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void* UI_destroy_method(UI_METHOD *p) {
|
||||
static inline void* ASN1_TYPE_free(ASN1_TYPE *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline int openssl_load_key_from_token(
|
||||
KeySourceType private_key_source_type,
|
||||
const char *private_key_source,
|
||||
const char *private_key,
|
||||
OpenSSLAskPasswordUI *ui,
|
||||
EVP_PKEY **ret_private_key) {
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
static inline void* ASN1_STRING_free(ASN1_STRING *p) {
|
||||
assert(p == NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TYPE*, ASN1_TYPE_free, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_STRING*, ASN1_STRING_free, NULL);
|
||||
|
||||
struct OpenSSLAskPasswordUI {
|
||||
AskPasswordRequest request;
|
||||
UI_METHOD *method;
|
||||
};
|
||||
|
||||
int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret);
|
||||
|
||||
static inline OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) {
|
||||
if (!ui)
|
||||
return NULL;
|
||||
|
||||
UI_destroy_method(ui->method);
|
||||
return mfree(ui);
|
||||
}
|
||||
OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL);
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "log.h"
|
||||
#include "pe-binary.h"
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "uki.h"
|
||||
|
||||
bool pe_header_is_64bit(const PeHeader *h) {
|
||||
assert(h);
|
||||
@ -284,3 +288,312 @@ bool pe_is_native(const PeHeader *pe_header) {
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Implements:
|
||||
*
|
||||
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
|
||||
* → Section "Calculating the PE Image Hash"
|
||||
*/
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
|
||||
uint8_t buffer[64*1024];
|
||||
|
||||
log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
|
||||
|
||||
while (size > 0) {
|
||||
size_t m = MIN(size, sizeof(buffer));
|
||||
ssize_t n;
|
||||
|
||||
n = pread(fd, buffer, m, offset);
|
||||
if (n < 0)
|
||||
return log_debug_errno(errno, "Failed to read file for hashing: %m");
|
||||
if ((size_t) n != m)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
|
||||
|
||||
if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
offset += m;
|
||||
size -= m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
|
||||
return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
|
||||
}
|
||||
#endif
|
||||
|
||||
int pe_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void **ret_hash,
|
||||
size_t *ret_hash_size) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
const IMAGE_DATA_DIRECTORY *certificate_table;
|
||||
struct stat st;
|
||||
uint64_t p, q;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(md);
|
||||
assert(ret_hash_size);
|
||||
assert(ret_hash);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat file: %m");
|
||||
r = stat_verify_regular(&st);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Not a regular file: %m");
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
|
||||
if (!certificate_table)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
/* Everything from beginning of file to CheckSum field in PE header */
|
||||
p = (uint64_t) dos_header->e_lfanew +
|
||||
offsetof(PeHeader, optional.CheckSum);
|
||||
r = hash_file(fd, mdctx, 0, p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
p += sizeof(le32_t);
|
||||
|
||||
/* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
|
||||
q = (uint64_t) dos_header->e_lfanew +
|
||||
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
|
||||
r = hash_file(fd, mdctx, p, q - p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
q += sizeof(IMAGE_DATA_DIRECTORY);
|
||||
|
||||
/* The rest of the header + the section table */
|
||||
p = pe_header->optional.SizeOfHeaders;
|
||||
if (p < q)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
|
||||
r = hash_file(fd, mdctx, q, p - q);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Sort by location in file */
|
||||
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p += section->SizeOfRawData;
|
||||
}
|
||||
|
||||
if ((uint64_t) st.st_size > p) {
|
||||
|
||||
if (st.st_size - p < certificate_table->Size)
|
||||
return log_debug_errno(errno, "No space for certificate table, refusing.");
|
||||
|
||||
r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If the file size is not a multiple of 8 bytes, pad the hash with zero bytes. */
|
||||
if (st.st_size % 8 != 0 && EVP_DigestUpdate(mdctx, (const uint8_t[8]) {}, 8 - (st.st_size % 8)) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
}
|
||||
|
||||
int hsz = EVP_MD_CTX_size(mdctx);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
_cleanup_free_ void *hash = malloc(hsz);
|
||||
if (!hash)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
*ret_hash = TAKE_PTR(hash);
|
||||
*ret_hash_size = hash_size;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate PE hash.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int pe_checksum(int fd, uint32_t *ret) {
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret);
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return log_debug_errno(errno, "Failed to stat file: %m");
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint32_t checksum = 0, checksum_offset = le32toh(dos_header->e_lfanew) + offsetof(PeHeader, optional.CheckSum);
|
||||
size_t off = 0;
|
||||
for (;;) {
|
||||
uint16_t buf[32*1024];
|
||||
|
||||
ssize_t n = pread(fd, buf, sizeof(buf), off);
|
||||
if (n == 0)
|
||||
break;
|
||||
if (n < 0)
|
||||
return log_debug_errno(errno, "Failed to read from PE file: %m");
|
||||
if (n % sizeof(uint16_t) != 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file");
|
||||
|
||||
for (size_t i = 0; i < (size_t) n / 2; i++) {
|
||||
if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum))
|
||||
continue;
|
||||
|
||||
uint16_t val = le16toh(buf[i]);
|
||||
|
||||
checksum += val;
|
||||
checksum = (checksum >> 16) + (checksum & 0xffff);
|
||||
}
|
||||
|
||||
off += n;
|
||||
}
|
||||
|
||||
checksum = (checksum >> 16) + (checksum & 0xffff);
|
||||
checksum += off;
|
||||
|
||||
*ret = checksum;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
|
||||
|
||||
static void section_hash_array_done(SectionHashArray *array) {
|
||||
assert(array);
|
||||
|
||||
for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
|
||||
free((*array)[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
int uki_hash(int fd,
|
||||
const EVP_MD *md,
|
||||
void* ret_hashes[static _UNIFIED_SECTION_MAX],
|
||||
size_t *ret_hash_size) {
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_(section_hash_array_done) SectionHashArray hashes = {};
|
||||
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
|
||||
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
|
||||
_cleanup_free_ PeHeader *pe_header = NULL;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ret_hashes);
|
||||
assert(ret_hash_size);
|
||||
|
||||
r = pe_load_headers(fd, &dos_header, &pe_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = pe_load_sections(fd, dos_header, pe_header, §ions);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
int hsz = EVP_MD_size(md);
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
ssize_t i;
|
||||
|
||||
n = memdup_suffix0(section->Name, sizeof(section->Name));
|
||||
if (!n)
|
||||
return log_oom_debug();
|
||||
|
||||
i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
|
||||
if (i < 0)
|
||||
continue;
|
||||
|
||||
if (hashes[i])
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if (!mdctx)
|
||||
return log_oom_debug();
|
||||
|
||||
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
|
||||
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, MIN(section->VirtualSize, section->SizeOfRawData));
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (section->SizeOfRawData < section->VirtualSize) {
|
||||
uint8_t zeroes[1024] = {};
|
||||
size_t remaining = section->VirtualSize - section->SizeOfRawData;
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t sz = MIN(sizeof(zeroes), remaining);
|
||||
|
||||
if (EVP_DigestUpdate(mdctx, zeroes, sz) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
|
||||
|
||||
remaining -= sz;
|
||||
}
|
||||
}
|
||||
|
||||
hashes[i] = malloc(hsz);
|
||||
if (!hashes[i])
|
||||
return log_oom_debug();
|
||||
|
||||
unsigned hash_size = (unsigned) hsz;
|
||||
if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
|
||||
|
||||
assert(hash_size == (unsigned) hsz);
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *hs = NULL;
|
||||
|
||||
hs = hexmem(hashes[i], hsz);
|
||||
log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(ret_hashes, hashes, sizeof(hashes));
|
||||
zero(hashes);
|
||||
*ret_hash_size = (unsigned) hsz;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate UKI hash.");
|
||||
#endif
|
||||
}
|
||||
|
@ -3,7 +3,12 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "openssl-util.h"
|
||||
#include "macro-fundamental.h"
|
||||
#include "sparse-endian.h"
|
||||
#include "uki.h"
|
||||
|
||||
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
|
||||
|
||||
/* When naming things we try to stay close to the official Windows APIs as per:
|
||||
* → https://learn.microsoft.com/en-us/windows/win32/debug/pe-format */
|
||||
@ -147,3 +152,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
|
||||
bool pe_is_addon(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections);
|
||||
|
||||
bool pe_is_native(const PeHeader *pe_header);
|
||||
|
||||
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
|
||||
|
||||
int pe_checksum(int fd, uint32_t *ret);
|
||||
|
||||
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);
|
||||
|
@ -263,6 +263,7 @@ class UkifyConfig:
|
||||
sections_by_name: dict[str, 'Section']
|
||||
sign_kernel: bool
|
||||
signing_engine: Optional[str]
|
||||
signing_provider: Optional[str]
|
||||
signtool: Optional[type['SignTool']]
|
||||
splash: Optional[Path]
|
||||
stub: Path
|
||||
@ -526,6 +527,45 @@ class SbSign(SignTool):
|
||||
return 'No signature table present' in info
|
||||
|
||||
|
||||
class SystemdSbSign(SignTool):
|
||||
@staticmethod
|
||||
def sign(input_f: str, output_f: str, opts: UkifyConfig) -> None:
|
||||
assert opts.sb_key is not None
|
||||
assert opts.sb_cert is not None
|
||||
|
||||
tool = find_tool(
|
||||
'systemd-sbsign',
|
||||
'/usr/lib/systemd/systemd-sbsign',
|
||||
opts=opts,
|
||||
msg='systemd-sbsign, required for signing, is not installed',
|
||||
)
|
||||
cmd = [
|
||||
tool,
|
||||
"sign",
|
||||
'--private-key', opts.sb_key,
|
||||
'--certificate', opts.sb_cert,
|
||||
*(
|
||||
['--private-key-source', f'engine:{opts.signing_engine}']
|
||||
if opts.signing_engine is not None
|
||||
else []
|
||||
),
|
||||
*(
|
||||
['--private-key-source', f'provider:{opts.signing_provider}']
|
||||
if opts.signing_provider is not None
|
||||
else []
|
||||
),
|
||||
input_f,
|
||||
'--output', output_f,
|
||||
] # fmt: skip
|
||||
|
||||
print('+', shell_join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
@staticmethod
|
||||
def verify(opts: UkifyConfig) -> bool:
|
||||
raise NotImplementedError('systemd-sbsign cannot yet verify if existing PE binaries are signed')
|
||||
|
||||
|
||||
def parse_banks(s: str) -> list[str]:
|
||||
banks = re.split(r',|\s+', s)
|
||||
# TODO: do some sanity checking here
|
||||
@ -711,6 +751,10 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
|
||||
assert pub_key
|
||||
extra += [f'--private-key-source=engine:{opts.signing_engine}']
|
||||
extra += [f'--certificate={pub_key}']
|
||||
elif opts.signing_provider is not None:
|
||||
assert pub_key
|
||||
extra += [f'--private-key-source=provider:{opts.signing_provider}']
|
||||
extra += [f'--certificate={pub_key}']
|
||||
elif pub_key:
|
||||
extra += [f'--public-key={pub_key}']
|
||||
extra += [f'--phase={phase_path}' for phase_path in group or ()]
|
||||
@ -965,9 +1009,9 @@ def make_uki(opts: UkifyConfig) -> None:
|
||||
if pcrpkey is None:
|
||||
if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1:
|
||||
pcrpkey = opts.pcr_public_keys[0]
|
||||
# If we are getting a certificate when using an engine, we need to convert it to public key
|
||||
# format
|
||||
if opts.signing_engine is not None and Path(pcrpkey).exists():
|
||||
# If we are getting a certificate when using an engine or provider, we need to convert it to
|
||||
# public key format.
|
||||
if (opts.signing_engine or opts.signing_provider) and Path(pcrpkey).exists():
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.x509 import load_pem_x509_certificate
|
||||
|
||||
@ -1477,6 +1521,8 @@ class SignToolAction(argparse.Action):
|
||||
setattr(namespace, 'signtool', SbSign)
|
||||
elif values == 'pesign':
|
||||
setattr(namespace, 'signtool', PeSign)
|
||||
elif values == 'systemd-sbsign':
|
||||
setattr(namespace, 'signtool', SystemdSbSign)
|
||||
else:
|
||||
raise ValueError(f"Unknown signtool '{values}' (this is unreachable)")
|
||||
|
||||
@ -1622,9 +1668,15 @@ CONFIG_ITEMS = [
|
||||
help='OpenSSL engine to use for signing',
|
||||
config_key='UKI/SigningEngine',
|
||||
),
|
||||
ConfigItem(
|
||||
'--signing-provider',
|
||||
metavar='PROVIDER',
|
||||
help='OpenSSL provider to use for signing',
|
||||
config_key='UKI/SigningProvider',
|
||||
),
|
||||
ConfigItem(
|
||||
'--signtool',
|
||||
choices=('sbsign', 'pesign'),
|
||||
choices=('sbsign', 'pesign', 'systemd-sbsign'),
|
||||
action=SignToolAction,
|
||||
dest='signtool',
|
||||
help=(
|
||||
@ -1637,7 +1689,7 @@ CONFIG_ITEMS = [
|
||||
ConfigItem(
|
||||
'--secureboot-private-key',
|
||||
dest='sb_key',
|
||||
help='required by --signtool=sbsign. Path to key file or engine-specific designation for SB signing',
|
||||
help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', # noqa: E501
|
||||
config_key='UKI/SecureBootPrivateKey',
|
||||
),
|
||||
ConfigItem(
|
||||
@ -1686,7 +1738,7 @@ CONFIG_ITEMS = [
|
||||
'--pcr-private-key',
|
||||
dest='pcr_private_keys',
|
||||
action='append',
|
||||
help='private part of the keypair or engine-specific designation for signing PCR signatures',
|
||||
help='private part of the keypair or engine/provider designation for signing PCR signatures',
|
||||
config_key='PCRSignature:/PCRPrivateKey',
|
||||
config_push=ConfigItem.config_set_group,
|
||||
),
|
||||
@ -1696,7 +1748,7 @@ CONFIG_ITEMS = [
|
||||
metavar='PATH',
|
||||
type=Path,
|
||||
action='append',
|
||||
help='public part of the keypair or engine-specific designation for signing PCR signatures',
|
||||
help='public part of the keypair or engine/provider designation for signing PCR signatures',
|
||||
config_key='PCRSignature:/PCRPublicKey',
|
||||
config_push=ConfigItem.config_set_group,
|
||||
),
|
||||
@ -1927,7 +1979,10 @@ def finalize_options(opts: argparse.Namespace) -> None:
|
||||
else:
|
||||
opts.stub = Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub')
|
||||
|
||||
if opts.signing_engine is None:
|
||||
if opts.signing_engine and opts.signing_provider:
|
||||
raise ValueError('Only one of --signing-engine= and --signing-provider= may be specified')
|
||||
|
||||
if opts.signing_engine is None and opts.signing_provider is None:
|
||||
if opts.sb_key:
|
||||
opts.sb_key = Path(opts.sb_key)
|
||||
if opts.sb_cert:
|
||||
@ -1940,11 +1995,12 @@ 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 not in (SbSign, SystemdSbSign):
|
||||
raise ValueError(
|
||||
f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' # noqa: E501
|
||||
)
|
||||
opts.signtool = SbSign
|
||||
if not opts.signtool:
|
||||
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:
|
||||
@ -1953,6 +2009,9 @@ def finalize_options(opts: argparse.Namespace) -> None:
|
||||
)
|
||||
opts.signtool = PeSign
|
||||
|
||||
if opts.signing_provider and opts.signtool != SystemdSbSign:
|
||||
raise ValueError('--signing-provider= can only be used with--signtool=systemd-sbsign')
|
||||
|
||||
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' # noqa: E501
|
||||
|
60
test/units/TEST-74-AUX-UTILS.sbsign.sh
Executable file
60
test/units/TEST-74-AUX-UTILS.sbsign.sh
Executable file
@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# shellcheck disable=SC2016
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
if ! command -v /usr/lib/systemd/systemd-sbsign >/dev/null; then
|
||||
echo "systemd-sbsign not found, skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -d /usr/lib/systemd/boot/efi ]]; then
|
||||
echo "systemd-boot is not installed, skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat >/tmp/openssl.conf <<EOF
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = DE
|
||||
ST = Test State
|
||||
L = Test Locality
|
||||
O = Org Name
|
||||
OU = Org Unit Name
|
||||
CN = Common Name
|
||||
emailAddress = test@email.com
|
||||
EOF
|
||||
|
||||
openssl req -config /tmp/openssl.conf -subj="/CN=waldo" \
|
||||
-x509 -sha256 -nodes -days 365 -newkey rsa:4096 \
|
||||
-keyout /tmp/sb.key -out /tmp/sb.crt
|
||||
|
||||
testcase_sign_systemd_boot() {
|
||||
if ! command -v sbverify >/dev/null; then
|
||||
echo "sbverify not found, skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
|
||||
|
||||
(! sbverify --cert /tmp/sb.crt "$SD_BOOT")
|
||||
/usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --private-key /tmp/sb.key --output /tmp/sdboot "$SD_BOOT"
|
||||
sbverify --cert /tmp/sb.crt /tmp/sdboot
|
||||
|
||||
# Make sure appending signatures to an existing certificate table works as well.
|
||||
/usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --private-key /tmp/sb.key --output /tmp/sdboot /tmp/sdboot
|
||||
sbverify --cert /tmp/sb.crt /tmp/sdboot
|
||||
}
|
||||
|
||||
testcase_validate_key() {
|
||||
/usr/lib/systemd/systemd-sbsign validate-key --certificate /tmp/sb.crt --private-key /tmp/sb.key
|
||||
}
|
||||
|
||||
run_testcases
|
Loading…
Reference in New Issue
Block a user