1
0
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:
Daan De Meyer 2024-11-06 17:38:10 +01:00 committed by GitHub
commit e5011dd239
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1352 additions and 414 deletions

111
man/systemd-sbsign.xml Normal file
View 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>

View File

@ -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>

View File

@ -2,7 +2,9 @@
[Build]
ToolsTreePackages=
meson
gcc
gperf
meson
mypy
pkgconf
ruff

135
src/boot/authenticode.h Normal file
View 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);

View File

@ -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)

View File

@ -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
View 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);

View File

@ -10,7 +10,6 @@ executables += [
'sources' : files(
'pcrlock.c',
'pcrlock-firmware.c',
'pehash.c',
),
'dependencies' : [
libm,

View File

@ -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"

View File

@ -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, &sections);
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, &sections);
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;
}

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -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, &sections);
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, &sections);
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
}

View File

@ -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);

View File

@ -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

View 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