1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-31 14:50:15 +03:00

sbsign: Add support for offline signing (#36485)

Add new options --prepare-offline-signing, --signed-data= and
--signed-data-signature= which allow for offline signing in a
similar manner to pesign.
This commit is contained in:
Lennart Poettering 2025-02-27 17:34:05 +01:00 committed by GitHub
commit 250118f3f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 563 additions and 186 deletions

2
TODO
View File

@ -127,6 +127,8 @@ Deprecations and removals:
Features:
* Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like.
* sd-boot: do something useful if we find exactly zero entries (ignoring items
such as reboot/poweroff/factory reset). Show a help text or so.

View File

@ -68,9 +68,10 @@
<varlistentry>
<term><command>pkcs7</command></term>
<listitem><para>This command generates a PKCS#7 signature that embeds the PKCS#1 signature (RSA)
provided with <option>--signature=</option> to an <option>--output=</option> file in PKCS#7 format
(p7s) using the certificate given with <option>--certificate=</option>.</para>
<listitem><para>This command embeds the PKCS#1 signature (RSA) provided with
<option>--signature=</option> in a PKCS#7 signature using the certificate given with
<option>--certificate=</option> and writes it to the file specified with <option>--output=</option>
in PKCS#7 format (p7s).</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>

View File

@ -60,7 +60,8 @@
<varlistentry>
<term><option>--output=<replaceable>PATH</replaceable></option></term>
<listitem><para>Specifies the path where to write the signed PE binary.</para>
<listitem><para>Specifies the path where to write the signed PE binary or the data to be signed
offline when using the <option>--prepare-offline-signing</option> option.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
@ -85,11 +86,63 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--prepare-offline-signing</option></term>
<listitem><para>When this option is specified, the <command>sign</command> command writes the data
that should be signed to the path specified with <option>--output=</option> instead of writing the
signed PE binary. This data can then be signed out of band after which the signature can be attached
to the PE binary using the <option>--signed-data=</option> and
<option>--signed-data-signature=</option> options.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--signed-data=<replaceable>PATH</replaceable></option></term>
<term><option>--signed-data-signature=<replaceable>PATH</replaceable></option></term>
<listitem><para>Configure the signed data (as written to the path specified with
<option>--output=</option> when using the <option>--prepare-offline-signing</option> option) and
corresponding signature for the <command>sign</command> command.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help"/>
<xi:include href="standard-options.xml" xpointer="version"/>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Offline EFI secure boot signing of a PE binary</title>
<para>The following does offline secure boot signing of systemd-boot:</para>
<programlisting>SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
# Extract the data that should be signed offline.
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate=secure-boot-certificate.pem \
--output=signed-data.bin \
--prepare-offline-signing \
"$SD_BOOT"
# Sign the data out-of-band. This step usually happens out-of-band on a separate system.
openssl dgst -sha256 -sign secure-boot-private-key.pem -out signed-data.sig signed-data.bin
# Attach the signed data and its signature to the systemd-boot PE binary.
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate=secure-boot-certificate.pem \
--output="$SD_BOOT.signed" \
--signed-data=signed-data.bin \
--signed-data-signature=signed-data.sig \
"$SD_BOOT"</programlisting>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">

View File

@ -13,6 +13,7 @@
#include "openssl-util.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "tmpfile-util.h"
#include "verbs.h"
static char *arg_private_key = NULL;
@ -338,80 +339,34 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) {
if (pkcs1_len == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "PKCS#1 file %s is empty", arg_signature);
/* Create PKCS7_SIGNER_INFO using X509 pubkey/digest NIDs */
_cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *signer_info = PKCS7_SIGNER_INFO_new();
if (!signer_info)
return log_oom();
if (ASN1_INTEGER_set(signer_info->version, 1) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 integer: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_NAME_set(&signer_info->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set issuer name: %s",
ERR_error_string(ERR_get_error(), NULL));
ASN1_INTEGER_free(signer_info->issuer_and_serial->serial);
signer_info->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate));
if (!signer_info->issuer_and_serial->serial)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set issuer serial: %s",
ERR_error_string(ERR_get_error(), NULL));
int x509_mdnid = 0, x509_pknid = 0;
if (X509_get_signature_info(certificate, &x509_mdnid, &x509_pknid, /* secbits= */ NULL, /* flags= */ NULL) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X.509 digest NID/PK: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_ALGOR_set0(signer_info->digest_alg, OBJ_nid2obj(x509_mdnid), V_ASN1_NULL, /* pval= */ NULL) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest alg: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_ALGOR_set0(signer_info->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, /* pval= */ NULL) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest enc alg: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Create new PKCS7 using X509 certificate */
_cleanup_(PKCS7_freep) PKCS7 *pkcs7 = PKCS7_new();
if (!pkcs7)
return log_oom();
if (PKCS7_set_type(pkcs7, NID_pkcs7_signed) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 type: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_content_new(pkcs7, NID_pkcs7_data) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 content: %s",
ERR_error_string(ERR_get_error(), NULL));
_cleanup_(PKCS7_freep) PKCS7 *pkcs7 = NULL;
PKCS7_SIGNER_INFO *signer_info;
r = pkcs7_new(certificate, /* private_key= */ NULL, &pkcs7, &signer_info);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS#7 context: %m");
if (PKCS7_set_detached(pkcs7, true) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 detached attribute: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add_certificate(pkcs7, certificate) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 certificate: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Add PKCS1 signature to PKCS7_SIGNER_INFO */
ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len);
/* Add PKCS7_SIGNER_INFO to PKCS7 */
if (PKCS7_add_signer(pkcs7, signer_info) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
TAKE_PTR(signer_info);
_cleanup_fclose_ FILE *output = fopen(arg_output, "we");
if (!output)
return log_error_errno(errno, "Could not open PKCS#7 output file %s: %m", arg_output);
_cleanup_fclose_ FILE *output = NULL;
_cleanup_free_ char *tmp = NULL;
r = fopen_tmpfile_linkable(arg_output, O_WRONLY|O_CLOEXEC, &tmp, &output);
if (r < 0)
return log_error_errno(r, "Failed to open temporary file: %m");
if (!i2d_PKCS7_fp(output, pkcs7))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write PKCS#7 file: %s",
ERR_error_string(ERR_get_error(), NULL));
r = flink_tmpfile(output, 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);
return 0;
}

View File

@ -7,7 +7,10 @@
#include "build.h"
#include "copy.h"
#include "efi-fundamental.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "log.h"
#include "main-func.h"
#include "openssl-util.h"
@ -25,12 +28,17 @@ static char *arg_certificate_source = 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 bool arg_prepare_offline_signing = false;
static char *arg_signed_data = NULL;
static char *arg_signed_data_signature = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_output, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_signed_data, freep);
STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep);
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
@ -78,16 +86,22 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CERTIFICATE_SOURCE,
ARG_PRIVATE_KEY,
ARG_PRIVATE_KEY_SOURCE,
ARG_PREPARE_OFFLINE_SIGNING,
ARG_SIGNED_DATA,
ARG_SIGNED_DATA_SIGNATURE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "output", required_argument, NULL, ARG_OUTPUT },
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
{ "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE },
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "output", required_argument, NULL, ARG_OUTPUT },
{ "certificate", required_argument, NULL, ARG_CERTIFICATE },
{ "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE },
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
{ "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING },
{ "signed-data", required_argument, NULL, ARG_SIGNED_DATA },
{ "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE },
{}
};
@ -145,6 +159,26 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_PREPARE_OFFLINE_SIGNING:
arg_prepare_offline_signing = true;
break;
case ARG_SIGNED_DATA: {
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data);
if (r < 0)
return r;
break;
}
case ARG_SIGNED_DATA_SIGNATURE: {
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data_signature);
if (r < 0)
return r;
break;
}
case '?':
return -EINVAL;
@ -155,118 +189,22 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_private_key_source && !arg_certificate)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified.");
if (!!arg_signed_data != !!arg_signed_data_signature)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--signed-data= and --signed-data-signature= must always be used together.");
if (arg_prepare_offline_signing && (arg_private_key || arg_signed_data || arg_signed_data_signature))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--prepare-offline-signing cannot be used with --private-key=, --signed-data= or --signed-data-signature=");
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;
static int spc_indirect_data_content_new(const void *digest, size_t digestsz, uint8_t **ret_idc, size_t *ret_idcsz) {
assert(digest);
assert(ret_idc);
assert(ret_idcsz);
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=");
if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
if (r < 0)
return r;
}
r = openssl_load_x509_certificate(
arg_certificate_source_type,
arg_certificate_source,
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) {
.tty_fd = -EBADF,
.id = "sbsign-private-key-pin",
.keyring = arg_private_key,
.credential = "sbsign.private-key-pin",
.until = USEC_INFINITY,
.hup_fd = -EBADF,
},
&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_error_errno(errno, "Failed to stat %s: %m", argv[1]);
r = stat_verify_regular(&st);
if (r < 0)
return log_error_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 = fchmod_umask(dstfd, 0666);
if (r < 0)
log_debug_errno(r, "Failed to change temporary file mode: %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]);
/* This function allocates and populates a new SpcIndirectDataContent object. See the authenticode
* spec https://aka.ms/AuthenticodeSpec for more information on the individual fields. */
/* <<<Obsolete>>> in unicode bytes. */
static const uint8_t obsolete[] = {
@ -334,7 +272,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, hash, hashsz) == 0)
if (ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s",
ERR_error_string(ERR_get_error(), NULL));
@ -344,6 +282,110 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert SpcIndirectDataContent to BER: %s",
ERR_error_string(ERR_get_error(), NULL));
*ret_idc = TAKE_PTR(idcraw);
*ret_idcsz = (size_t) idcrawsz;
return 0;
}
static int asn1_timestamp(ASN1_TIME **ret) {
ASN1_TIME *time;
uint64_t epoch = UINT64_MAX;
int r;
assert(ret);
r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
if (r != -ENXIO)
log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
if (epoch == UINT64_MAX) {
time = X509_gmtime_adj(NULL, 0);
if (!time)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get current time: %s",
ERR_error_string(ERR_get_error(), NULL));
} else {
time = ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC));
if (!time)
return log_oom();
}
*ret = TAKE_PTR(time);
return 0;
}
static int pkcs7_new_with_attributes(
X509 *certificate,
EVP_PKEY *private_key,
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
PKCS7 **ret_p7,
PKCS7_SIGNER_INFO **ret_si) {
int r;
/* This function sets up a new PKCS#7 signing context with the signed attributes required for
* authenticode signing. */
assert(certificate);
assert(ret_p7);
assert(ret_si);
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new(certificate, private_key, &p7, &si);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS# context: %m");
if (signed_attributes) {
si->auth_attr = signed_attributes;
*ret_p7 = TAKE_PTR(p7);
*ret_si = TAKE_PTR(si);
return 0;
}
/* Add an empty SMIMECAP attribute to indicate we don't have any SMIME capabilities. */
_cleanup_(x509_algor_free_manyp) STACK_OF(X509_ALGOR) *smcap = sk_X509_ALGOR_new_null();
if (!smcap)
return log_oom();
if (PKCS7_add_attrib_smimecap(si, smcap) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add_attrib_content_type(si, NULL) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add content type signed attribute to signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
r = asn1_timestamp(&time);
if (r < 0)
return r;
if (PKCS7_add0_attrib_signing_time(si, time) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add signing time signed attribute to signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
TAKE_PTR(time);
ASN1_OBJECT *idc = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!idc)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 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));
*ret_p7 = TAKE_PTR(p7);
*ret_si = TAKE_PTR(si);
return 0;
}
static int pkcs7_populate_data_bio(PKCS7* p7, const void *data, size_t size, BIO **ret) {
assert(ret);
_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",
@ -351,10 +393,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
int tag, class;
long psz;
const uint8_t *p = idcraw;
const uint8_t *p = data;
/* 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)
if (ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s",
ERR_error_string(ERR_get_error(), NULL));
@ -362,15 +404,211 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
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",
*ret = TAKE_PTR(bio);
return 0;
}
static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *si) {
assert(p7);
assert(data);
assert(si);
BIO *mdbio = BIO_find_type(data, BIO_TYPE_MD);
if (!mdbio)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to find digest bio: %s",
ERR_error_string(ERR_get_error(), NULL));
EVP_MD_CTX *mdc;
if (BIO_get_md_ctx(mdbio, &mdc) <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest context from bio: %s",
ERR_error_string(ERR_get_error(), NULL));
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned digestsz;
if (EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add PKCS9 message digest signed attribute to signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
return 0;
}
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;
_cleanup_(x509_attribute_free_manyp) STACK_OF(X509_ATTRIBUTE) *signed_attributes = NULL;
_cleanup_(iovec_done) struct iovec signed_attributes_signature = {};
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 && !arg_signed_data_signature && !arg_prepare_offline_signing)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No private key or signed data signature specified, use --private-key= or --signed-data-signature=.");
if (!arg_output)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No output specified, use --output=");
if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) {
r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate);
if (r < 0)
return r;
}
r = openssl_load_x509_certificate(
arg_certificate_source_type,
arg_certificate_source,
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) {
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) {
.tty_fd = -EBADF,
.id = "sbsign-private-key-pin",
.keyring = arg_private_key,
.credential = "sbsign.private-key-pin",
.until = USEC_INFINITY,
.hup_fd = -EBADF,
},
&private_key,
&ui);
if (r < 0)
return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
}
if (arg_signed_data) {
_cleanup_free_ void *content = NULL;
size_t contentsz;
r = read_full_file(arg_signed_data, (char**) &content, &contentsz);
if (r < 0)
return log_error_errno(r, "Failed to read signed attributes file '%s': %m", arg_signed_data);
const uint8_t *p = content;
if (!ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN)))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s",
ERR_error_string(ERR_get_error(), NULL));
}
if (arg_signed_data_signature) {
_cleanup_free_ void *content = NULL;
size_t contentsz;
r = read_full_file(arg_signed_data_signature, (char**) &content, &contentsz);
if (r < 0)
return log_error_errno(r, "Failed to read signed attributes signature file '%s': %m", arg_signed_data_signature);
signed_attributes_signature = IOVEC_MAKE(TAKE_PTR(content), contentsz);
}
_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_error_errno(errno, "Failed to stat %s: %m", argv[1]);
r = stat_verify_regular(&st);
if (r < 0)
return log_error_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(dstfd, "Failed to open temporary file: %m");
r = fchmod_umask(dstfd, 0666);
if (r < 0)
log_debug_errno(r, "Failed to change temporary file mode: %m");
_cleanup_free_ void *pehash = NULL;
size_t pehashsz;
r = pe_hash(srcfd, EVP_sha256(), &pehash, &pehashsz);
if (r < 0)
return log_error_errno(r, "Failed to hash PE binary %s: %m", argv[0]);
_cleanup_free_ uint8_t *idcraw = NULL;
size_t idcrawsz = 0; /* avoid false maybe-uninitialized warning */
r = spc_indirect_data_content_new(pehash, pehashsz, &idcraw, &idcrawsz);
if (r < 0)
return r;
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new_with_attributes(certificate, private_key, signed_attributes, &p7, &si);
if (r < 0)
return r;
TAKE_PTR(signed_attributes);
if (arg_prepare_offline_signing) {
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
r = pkcs7_add_digest_attribute(p7, bio, si);
if (r < 0)
return r;
_cleanup_free_ unsigned char *abuf = NULL;
int alen = ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, ASN1_ITEM_rptr(PKCS7_ATTR_SIGN));
if (alen < 0 || !abuf)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert signed attributes ASN.1 to DER: %s",
ERR_error_string(ERR_get_error(), NULL));
r = loop_write(dstfd, abuf, alen);
if (r < 0)
return log_error_errno(r, "Failed to write PKCS#7 DER-encoded signed attributes blob to temporary file: %m");
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 PKCS#7 DER-encoded signed attributes blob to %s", arg_output);
return 0;
} else if (iovec_is_set(&signed_attributes_signature))
ASN1_STRING_set0(si->enc_digest, TAKE_PTR(signed_attributes_signature.iov_base), signed_attributes_signature.iov_len);
else {
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = pkcs7_populate_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
if (PKCS7_dataFinal(p7, bio) == 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);
p7c->type = OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!p7c->type)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get SpcIndirectDataContent object: %s",
ERR_error_string(ERR_get_error(), NULL));
@ -411,6 +649,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
if (!certificate_table)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
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);
off_t end = st.st_size;
ssize_t n;

View File

@ -1125,6 +1125,86 @@ int digest_and_sign(
return 0;
}
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
assert(certificate);
assert(ret_p7);
/* This function sets up a new PKCS7 signing context. If a private key is provided, the context is
* set up for "in-band" signing with PKCS7_dataFinal(). If a private key is not provided, the context
* is set up for "out-of-band" signing, meaning the signature has to be provided by the user and
* copied into the signer info's "enc_digest" field. */
_cleanup_(PKCS7_freep) PKCS7 *p7 = PKCS7_new();
if (!p7)
return log_oom();
if (PKCS7_set_type(p7, NID_pkcs7_signed) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 type: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_content_new(p7, NID_pkcs7_data) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 content: %s",
ERR_error_string(ERR_get_error(), NULL));
if (PKCS7_add_certificate(p7, certificate) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 certificate: %s",
ERR_error_string(ERR_get_error(), NULL));
int x509_mdnid = 0, x509_pknid = 0;
if (X509_get_signature_info(certificate, &x509_mdnid, &x509_pknid, NULL, NULL) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X509 digest NID: %s",
ERR_error_string(ERR_get_error(), NULL));
const EVP_MD *md = EVP_get_digestbynid(x509_mdnid);
if (!md)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm via digest NID");
_cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = PKCS7_SIGNER_INFO_new();
if (!si)
return log_oom();
if (private_key) {
if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, EVP_get_digestbynid(x509_mdnid)) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
} else {
if (ASN1_INTEGER_set(si->version, 1) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info version: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_NAME_set(&si->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info issuer: %s",
ERR_error_string(ERR_get_error(), NULL));
ASN1_INTEGER_free(si->issuer_and_serial->serial);
si->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate));
if (!si->issuer_and_serial->serial)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info serial: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(x509_mdnid), V_ASN1_NULL, NULL) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info digest algorithm: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_ALGOR_set0(si->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info signing algorithm: %s",
ERR_error_string(ERR_get_error(), NULL));
}
if (PKCS7_add_signer(p7, si) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
*ret_p7 = TAKE_PTR(p7);
if (ret_si)
/* We do not pass ownership here, 'si' object remains owned by 'p7' object. */
*ret_si = si;
TAKE_PTR(si);
return 0;
}
# if PREFER_OPENSSL
int string_hashsum(
const char *s,

View File

@ -68,6 +68,27 @@ 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);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_TIME*, ASN1_TIME_free, NULL);
static inline STACK_OF(X509_ALGOR) *x509_algor_free_many(STACK_OF(X509_ALGOR) *attrs) {
if (!attrs)
return NULL;
sk_X509_ALGOR_pop_free(attrs, X509_ALGOR_free);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ALGOR)*, x509_algor_free_many, NULL);
static inline STACK_OF(X509_ATTRIBUTE) *x509_attribute_free_many(STACK_OF(X509_ATTRIBUTE) *attrs) {
if (!attrs)
return NULL;
sk_X509_ATTRIBUTE_pop_free(attrs, X509_ATTRIBUTE_free);
return NULL;
}
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(STACK_OF(X509_ATTRIBUTE)*, x509_attribute_free_many, NULL);
#if OPENSSL_VERSION_MAJOR >= 3
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL);
@ -145,6 +166,8 @@ 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 pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si);
#else
typedef struct X509 X509;

View File

@ -39,7 +39,7 @@ openssl req -config /tmp/openssl.conf -subj="/CN=waldo" \
testcase_sign_systemd_boot() {
if ! command -v sbverify >/dev/null; then
echo "sbverify not found, skipping."
exit 0
return 0
fi
SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
@ -53,4 +53,25 @@ testcase_sign_systemd_boot() {
sbverify --cert /tmp/sb.crt /tmp/sdboot
}
testcase_sign_systemd_boot_offline() {
if ! command -v sbverify >/dev/null; then
echo "sbverify not found, skipping."
return 0
fi
SD_BOOT="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)"
/usr/lib/systemd/systemd-sbsign sign --certificate /tmp/sb.crt --output /tmp/signed-data.bin --prepare-offline-signing "$SD_BOOT"
openssl dgst -sha256 -sign /tmp/sb.key -out /tmp/signed-data.sig /tmp/signed-data.bin
/usr/lib/systemd/systemd-sbsign \
sign \
--certificate /tmp/sb.crt \
--output /tmp/sdboot \
--signed-data /tmp/signed-data.bin \
--signed-data-signature /tmp/signed-data.sig \
"$SD_BOOT"
sbverify --cert /tmp/sb.crt /tmp/sdboot
}
run_testcases