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:
parent
0bf70b1984
commit
5f163921e9
95
man/systemd-sbsign.xml
Normal file
95
man/systemd-sbsign.xml
Normal 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
135
src/boot/authenticode.h
Normal file
@ -0,0 +1,135 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <openssl/asn1t.h>
|
||||
|
||||
#include "macro.h"
|
||||
|
||||
#define SPC_INDIRECT_DATA_OBJID "1.3.6.1.4.1.311.2.1.4"
|
||||
#define SPC_PE_IMAGE_DATA_OBJID "1.3.6.1.4.1.311.2.1.15"
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *type;
|
||||
ASN1_TYPE *value;
|
||||
} SpcAttributeTypeAndOptionalValue;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
|
||||
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
|
||||
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OBJECT *algorithm;
|
||||
ASN1_TYPE *parameters;
|
||||
} AlgorithmIdentifier;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
ASN1_SEQUENCE(AlgorithmIdentifier) = {
|
||||
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
|
||||
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
|
||||
} ASN1_SEQUENCE_END(AlgorithmIdentifier)
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier);
|
||||
|
||||
typedef struct {
|
||||
AlgorithmIdentifier *digestAlgorithm;
|
||||
ASN1_OCTET_STRING *digest;
|
||||
} DigestInfo;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
ASN1_SEQUENCE(DigestInfo) = {
|
||||
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
|
||||
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
|
||||
} ASN1_SEQUENCE_END(DigestInfo);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(DigestInfo);
|
||||
|
||||
typedef struct {
|
||||
SpcAttributeTypeAndOptionalValue *data;
|
||||
DigestInfo *messageDigest;
|
||||
} SpcIndirectDataContent;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
ASN1_SEQUENCE(SpcIndirectDataContent) = {
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
|
||||
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
|
||||
} ASN1_SEQUENCE_END(SpcIndirectDataContent);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_BMPSTRING *unicode;
|
||||
ASN1_IA5STRING *ascii;
|
||||
} value;
|
||||
} SpcString;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcString);
|
||||
|
||||
ASN1_CHOICE(SpcString) = {
|
||||
ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
|
||||
ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
|
||||
} ASN1_CHOICE_END(SpcString);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcString);
|
||||
|
||||
typedef struct {
|
||||
ASN1_OCTET_STRING *classId;
|
||||
ASN1_OCTET_STRING *serializedData;
|
||||
} SpcSerializedObject;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcSerializedObject);
|
||||
|
||||
ASN1_SEQUENCE(SpcSerializedObject) = {
|
||||
ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
|
||||
ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
|
||||
} ASN1_SEQUENCE_END(SpcSerializedObject);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcSerializedObject);
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
union {
|
||||
ASN1_IA5STRING *url;
|
||||
SpcSerializedObject *moniker;
|
||||
SpcString *file;
|
||||
} value;
|
||||
} SpcLink;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcLink);
|
||||
|
||||
ASN1_CHOICE(SpcLink) = {
|
||||
ASN1_IMP_OPT(SpcLink, value.url, ASN1_IA5STRING, 0),
|
||||
ASN1_IMP_OPT(SpcLink, value.moniker, SpcSerializedObject, 1),
|
||||
ASN1_EXP_OPT(SpcLink, value.file, SpcString, 2)
|
||||
} ASN1_CHOICE_END(SpcLink);
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcLink);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
|
||||
|
||||
typedef struct {
|
||||
ASN1_BIT_STRING *flags;
|
||||
SpcLink *file;
|
||||
} SpcPeImageData;
|
||||
|
||||
DECLARE_ASN1_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
ASN1_SEQUENCE(SpcPeImageData) = {
|
||||
ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
|
||||
ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
|
||||
} ASN1_SEQUENCE_END(SpcPeImageData)
|
||||
|
||||
IMPLEMENT_ASN1_FUNCTIONS(SpcPeImageData);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
|
@ -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
488
src/boot/sbsign.c
Normal 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);
|
@ -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;
|
||||
|
@ -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];
|
||||
|
||||
|
@ -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);
|
||||
|
56
test/units/TEST-74-AUX-UTILS.sbsign.sh
Executable file
56
test/units/TEST-74-AUX-UTILS.sbsign.sh
Executable 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
|
Loading…
x
Reference in New Issue
Block a user