1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-06 00:58:29 +03:00

Introduce systemd-sbsign to do secure boot signing

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-05 00:36:32 +01:00
parent 0bf70b1984
commit 5f163921e9
8 changed files with 852 additions and 4 deletions

95
man/systemd-sbsign.xml Normal file
View File

@ -0,0 +1,95 @@
<?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>
</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>

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

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

488
src/boot/sbsign.c Normal file
View File

@ -0,0 +1,488 @@
/* 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"
"\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 run(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "sign", 2, 2, 0, verb_sign },
{}
};
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

@ -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);
@ -140,13 +141,25 @@ 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* ASN1_TYPE_free(ASN1_TYPE *p) {
assert(p == NULL);
return NULL;
}
static inline void* ASN1_STRING_free(ASN1_STRING *p) {
assert(p == NULL);
return NULL;
}
@ -155,6 +168,8 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) {
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(ASN1_TYPE*, ASN1_TYPE_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_STRING*, ASN1_STRING_free, NULL);
struct OpenSSLAskPasswordUI {
AskPasswordRequest request;

View File

@ -12,8 +12,6 @@
#include "string-table.h"
#include "string-util.h"
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
bool pe_header_is_64bit(const PeHeader *h) {
assert(h);
@ -444,6 +442,55 @@ int pe_hash(int fd,
#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];

View File

@ -8,6 +8,8 @@
#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 */
@ -153,4 +155,6 @@ 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

@ -0,0 +1,56 @@
#!/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
}
run_testcases