sbsign: move the Authenticode code to src/shared

ukify signs UKIs and kernels the same way systemd-sbsign does. Move the
ASN.1 types and the PKCS#7 helpers from sbsign into
src/shared/authenticode.c, and add authenticode_sign_pe(), which signs a
PE file in place.

Preparation for rewriting ukify in C.
This commit is contained in:
Daan De Meyer authored and Yu Watanabe committed 2026-10-25 05:35:31 +09:00
1 parent a5ffbaed3e
commit 9c76ca4cec
9 files changed
+574 -467

No files matched your search

-125
View File
@@ -1,125 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <openssl/asn1t.h>
#include "authenticode.h"
#include "crypto-util.h"
/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_ASN1_FUNCTIONS macros expand to code that references
* libcrypto symbols directly, which would force us to link this object against libcrypto and defeat the
* dlopen approach used everywhere else. We work around that in two different ways depending on where the
* reference appears in the macro expansion:
*
* - ASN1_item_new/ASN1_item_free/ASN1_item_d2i/ASN1_item_i2d are called from the bodies of the functions
* generated by IMPLEMENT_ASN1_FUNCTIONS. We can simply #define them to the matching sym_* variants, so
* the generated bodies end up calling our dlopen'd pointers.
*
* - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the
* static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static
* initializers can only contain constant expressions, so we can't point them at the sym_* variables
* (which are non-const globals filled in at dlopen time). Instead we define static trampoline
* functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding
* sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are
* emitted.
*
* Note that this file must only call these redefined macros indirectly, via the IMPLEMENT_ASN1_FUNCTIONS
* expansions, and callers of those generated wrappers (e.g. sbsign.c) must have dlopen_libcrypto()
* before invoking them, otherwise the sym_* pointers will still be NULL. */
static const ASN1_ITEM* openssl_ASN1_ANY_it(void) {
assert(sym_ASN1_ANY_it);
return sym_ASN1_ANY_it();
}
static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) {
assert(sym_ASN1_BIT_STRING_it);
return sym_ASN1_BIT_STRING_it();
}
static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) {
assert(sym_ASN1_BMPSTRING_it);
return sym_ASN1_BMPSTRING_it();
}
static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) {
assert(sym_ASN1_IA5STRING_it);
return sym_ASN1_IA5STRING_it();
}
static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) {
assert(sym_ASN1_OBJECT_it);
return sym_ASN1_OBJECT_it();
}
static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) {
assert(sym_ASN1_OCTET_STRING_it);
return sym_ASN1_OCTET_STRING_it();
}
#define ASN1_item_new sym_ASN1_item_new
#define ASN1_item_free sym_ASN1_item_free
#define ASN1_item_d2i sym_ASN1_item_d2i
#define ASN1_item_i2d sym_ASN1_item_i2d
#define ASN1_ANY_it openssl_ASN1_ANY_it
#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it
#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it
#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it
#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it
#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
} ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
IMPLEMENT_ASN1_FUNCTIONS(SpcAttributeTypeAndOptionalValue);
ASN1_SEQUENCE(AlgorithmIdentifier) = {
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
} ASN1_SEQUENCE_END(AlgorithmIdentifier)
IMPLEMENT_ASN1_FUNCTIONS(AlgorithmIdentifier);
ASN1_SEQUENCE(DigestInfo) = {
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
} ASN1_SEQUENCE_END(DigestInfo);
IMPLEMENT_ASN1_FUNCTIONS(DigestInfo);
ASN1_SEQUENCE(SpcIndirectDataContent) = {
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
} ASN1_SEQUENCE_END(SpcIndirectDataContent);
IMPLEMENT_ASN1_FUNCTIONS(SpcIndirectDataContent);
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);
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);
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);
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);
-80
View File
@@ -1,80 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL
# error "missing libopenssl_cflags in meson dependency."
#endif
#include <openssl/asn1.h>
#include "forward.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);
typedef struct {
ASN1_OBJECT *algorithm;
ASN1_TYPE *parameters;
} AlgorithmIdentifier;
DECLARE_ASN1_FUNCTIONS(AlgorithmIdentifier);
typedef struct {
AlgorithmIdentifier *digestAlgorithm;
ASN1_OCTET_STRING *digest;
} DigestInfo;
DECLARE_ASN1_FUNCTIONS(DigestInfo);
typedef struct {
SpcAttributeTypeAndOptionalValue *data;
DigestInfo *messageDigest;
} SpcIndirectDataContent;
DECLARE_ASN1_FUNCTIONS(SpcIndirectDataContent);
typedef struct {
int type;
union {
ASN1_BMPSTRING *unicode;
ASN1_IA5STRING *ascii;
} value;
} SpcString;
DECLARE_ASN1_FUNCTIONS(SpcString);
typedef struct {
ASN1_OCTET_STRING *classId;
ASN1_OCTET_STRING *serializedData;
} SpcSerializedObject;
DECLARE_ASN1_FUNCTIONS(SpcSerializedObject);
typedef struct {
int type;
union {
ASN1_IA5STRING *url;
SpcSerializedObject *moniker;
SpcString *file;
} value;
} SpcLink;
DECLARE_ASN1_FUNCTIONS(SpcLink);
typedef struct {
ASN1_BIT_STRING *flags;
SpcLink *file;
} SpcPeImageData;
DECLARE_ASN1_FUNCTIONS(SpcPeImageData);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
+1 -4
View File
@@ -6,10 +6,7 @@ executables += [
'conditions' : [
'HAVE_OPENSSL',
],
'sources' : files(
'sbsign.c',
'authenticode.c',
),
'sources' : files('sbsign.c'),
'dependencies' : libopenssl_cflags,
},
]
+14 -258
View File
@@ -15,7 +15,6 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "install-file.h"
#include "io-util.h"
#include "log.h"
#include "main-func.h"
@@ -146,225 +145,6 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
return 1;
}
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);
/* 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[] = {
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 = sym_ASN1_BMPSTRING_new();
if (!link->value.file->value.unicode)
return log_oom();
if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string");
_cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
if (!peid)
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_openssl_errors(LOG_ERR, "Failed to convert SpcPeImageData to BER");
_cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
idc->data->value = sym_ASN1_TYPE_new();
if (!idc->data->value)
return log_oom();
idc->data->value->type = V_ASN1_SEQUENCE;
idc->data->value->value.sequence = sym_ASN1_STRING_new();
if (!idc->data->value->value.sequence)
return log_oom();
idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1);
if (!idc->data->type)
return log_openssl_errors(LOG_ERR, "Failed to get SpcPeImageData object");
if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz))
return log_openssl_errors(LOG_ERR, "Failed to set ASN1_STRING data.");
idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256);
if (!idc->messageDigest->digestAlgorithm->algorithm)
return log_openssl_errors(LOG_ERR, "Failed to get SHA256 object");
idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new();
if (!idc->messageDigest->digestAlgorithm->parameters)
return log_oom();
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0)
return log_openssl_errors(LOG_ERR, "Failed to set digest");
_cleanup_free_ uint8_t *idcraw = NULL;
int idcrawsz = i2d_SpcIndirectDataContent(idc, &idcraw);
if (idcrawsz < 0)
return log_openssl_errors(LOG_ERR, "Failed to convert SpcIndirectDataContent to BER");
*ret_idc = TAKE_PTR(idcraw);
*ret_idcsz = (size_t) idcrawsz;
return 0;
}
static int asn1_timestamp(ASN1_TIME **ret) {
ASN1_TIME *time;
assert(ret);
usec_t epoch = parse_source_date_epoch();
if (epoch == USEC_INFINITY) {
time = sym_X509_gmtime_adj(NULL, 0);
if (!time)
return log_openssl_errors(LOG_ERR, "Failed to get current time");
} else {
time = sym_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,
const char *hash_algorithm,
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, hash_algorithm, &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 = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null();
if (!smcap)
return log_oom();
if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0)
return log_openssl_errors(LOG_ERR, "Failed to add smimecap signed attribute to signer info");
if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0)
return log_openssl_errors(LOG_ERR, "Failed to add content type signed attribute to signer info");
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
r = asn1_timestamp(&time);
if (r < 0)
return r;
if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0)
return log_openssl_errors(LOG_ERR, "Failed to add signing time signed attribute to signer info");
TAKE_PTR(time);
ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!idc)
return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object");
if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
return log_openssl_errors(LOG_ERR, "Failed to add signed attribute to pkcs7 signer info");
*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 = sym_PKCS7_dataInit(p7, NULL);
if (!bio)
return log_openssl_errors(LOG_ERR, "Failed to create PKCS7 data bio");
int tag, class;
long psz;
const uint8_t *p = data;
/* This function weirdly enough reports errors by setting the 0x80 bit in its return value. */
if (sym_ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80)
return log_openssl_errors(LOG_ERR, "Failed to parse ASN.1 object");
if (sym_BIO_write(bio, p, psz) < 0)
return log_openssl_errors(LOG_ERR, "Failed to write to PKCS7 data bio");
*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 = sym_BIO_find_type(data, BIO_TYPE_MD);
if (!mdbio)
return log_openssl_errors(LOG_ERR, "Failed to find digest bio");
EVP_MD_CTX *mdc;
if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0)
return log_openssl_errors(LOG_ERR, "Failed to get digest context from bio");
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned digestsz;
if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
return log_openssl_errors(LOG_ERR, "Failed to get digest");
if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
return log_openssl_errors(LOG_ERR, "Failed to add PKCS9 message digest signed attribute to signer info");
return 0;
}
VERB(verb_sign, "sign", "EXEFILE\0", 2, 2, 0,
"Sign the given binary for EFI Secure Boot");
static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
@@ -477,27 +257,27 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
_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);
r = authenticode_spc_indirect_data_content_new(pehash, pehashsz, &idcraw, &idcrawsz);
if (r < 0)
return r;
return log_error_errno(r, "Failed to create SpcIndirectDataContent: %m");
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new_with_attributes(certificate, private_key, /* hash_algorithm= */ NULL, signed_attributes, &p7, &si);
r = authenticode_pkcs7_new(certificate, private_key, signed_attributes, &p7, &si);
if (r < 0)
return r;
return log_error_errno(r, "Failed to create PKCS#7 signing context: %m");
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);
r = authenticode_pkcs7_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
return log_error_errno(r, "Failed to populate PKCS#7 data: %m");
r = pkcs7_add_digest_attribute(p7, bio, si);
r = authenticode_pkcs7_add_digest_attribute(bio, si);
if (r < 0)
return r;
return log_error_errno(r, "Failed to add digest attribute: %m");
_cleanup_free_ unsigned char *abuf = NULL;
int alen = sym_ASN1_item_i2d((ASN1_VALUE *)si->auth_attr, &abuf, sym_PKCS7_ATTR_SIGN_it());
@@ -520,43 +300,19 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) {
sym_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);
r = authenticode_pkcs7_data_bio(p7, idcraw, idcrawsz, &bio);
if (r < 0)
return r;
return log_error_errno(r, "Failed to populate PKCS#7 data: %m");
if (sym_PKCS7_dataFinal(p7, bio) == 0)
return log_openssl_errors(LOG_ERR, "Failed to sign data");
}
_cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new();
if (!p7c)
return log_oom();
p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!p7c->type)
return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object");
p7c->d.other = sym_ASN1_TYPE_new();
if (!p7c->d.other)
return log_oom();
p7c->d.other->type = V_ASN1_SEQUENCE;
p7c->d.other->value.sequence = sym_ASN1_STRING_new();
if (!p7c->d.other->value.sequence)
return log_oom();
if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0)
return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string");
if (sym_PKCS7_set_content(p7, p7c) == 0)
return log_openssl_errors(LOG_ERR, "Failed to set PKCS7 data");
TAKE_PTR(p7c);
_cleanup_free_ uint8_t *sig = NULL;
int sigsz = sym_i2d_PKCS7(p7, &sig);
if (sigsz < 0)
return log_openssl_errors(LOG_ERR, "Failed to convert PKCS7 signature to DER");
size_t sigsz;
r = authenticode_pkcs7_finalize(p7, idcraw, idcrawsz, &sig, &sigsz);
if (r < 0)
return log_error_errno(r, "Failed to finalize PKCS#7 signature: %m");
r = copy_bytes(srcfd, dstfd, UINT64_MAX, /* copy_flags= */ 0);
if (r < 0)
+486
View File
@@ -0,0 +1,486 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <openssl/asn1t.h>
#include "alloc-util.h"
#include "authenticode.h"
#include "crypto-util.h"
#include "install-file.h"
#include "log.h"
#include "pe-binary.h"
#include "pe-writer.h"
#include "time-util.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;
typedef struct {
ASN1_OBJECT *algorithm;
ASN1_TYPE *parameters;
} AlgorithmIdentifier;
typedef struct {
AlgorithmIdentifier *digestAlgorithm;
ASN1_OCTET_STRING *digest;
} DigestInfo;
typedef struct {
SpcAttributeTypeAndOptionalValue *data;
DigestInfo *messageDigest;
} SpcIndirectDataContent;
typedef struct {
int type;
union {
ASN1_BMPSTRING *unicode;
ASN1_IA5STRING *ascii;
} value;
} SpcString;
typedef struct {
ASN1_OCTET_STRING *classId;
ASN1_OCTET_STRING *serializedData;
} SpcSerializedObject;
typedef struct {
int type;
union {
ASN1_IA5STRING *url;
SpcSerializedObject *moniker;
SpcString *file;
} value;
} SpcLink;
typedef struct {
ASN1_BIT_STRING *flags;
SpcLink *file;
} SpcPeImageData;
/* OpenSSL's ASN1_SEQUENCE/ASN1_CHOICE/IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS macros expand to code that
* references libcrypto symbols directly, which would force us to link this object against libcrypto and
* defeat the dlopen approach used everywhere else. We work around that in two different ways depending on
* where the reference appears in the macro expansion:
*
* - ASN1_item_new/ASN1_item_free are called from the bodies of the functions generated by
* IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS. We #define them to the matching sym_* variants, so
* the generated bodies end up calling our dlopen'd pointers.
*
* - ASN1_ANY_it/ASN1_BIT_STRING_it/... (the "_it" getters) are embedded as function pointers in the
* static const ASN1_ADB / ASN1_TEMPLATE tables emitted by the ASN1_SEQUENCE/ASN1_CHOICE macros. Static
* initializers can only contain constant expressions, so we can't point them at the sym_* variables
* (which are non-const globals filled in at dlopen time). Instead we define static trampoline
* functions (openssl_ASN1_*_it) whose addresses are constant, each forwarding to the corresponding
* sym_* pointer at runtime, and #define the OpenSSL names to our trampolines before the tables are
* emitted.
*
* A function in this file must call dlopen_libcrypto() before it calls SpcLink_new() or any other generated
* function. Otherwise the sym_* pointers are still NULL and the call crashes.
*
* The ASN.1 items and functions are static, so that libsystemd-shared doesn't export them. GCC and clang
* fail the build on an unused static function because of -Werror=unused-function. For that reason, we only
* generate the allocation functions that this file calls, and we call sym_ASN1_item_i2d() directly instead
* of generating i2d_*() functions. */
static const ASN1_ITEM* openssl_ASN1_ANY_it(void) {
assert(sym_ASN1_ANY_it);
return sym_ASN1_ANY_it();
}
static const ASN1_ITEM* openssl_ASN1_BIT_STRING_it(void) {
assert(sym_ASN1_BIT_STRING_it);
return sym_ASN1_BIT_STRING_it();
}
static const ASN1_ITEM* openssl_ASN1_BMPSTRING_it(void) {
assert(sym_ASN1_BMPSTRING_it);
return sym_ASN1_BMPSTRING_it();
}
static const ASN1_ITEM* openssl_ASN1_IA5STRING_it(void) {
assert(sym_ASN1_IA5STRING_it);
return sym_ASN1_IA5STRING_it();
}
static const ASN1_ITEM* openssl_ASN1_OBJECT_it(void) {
assert(sym_ASN1_OBJECT_it);
return sym_ASN1_OBJECT_it();
}
static const ASN1_ITEM* openssl_ASN1_OCTET_STRING_it(void) {
assert(sym_ASN1_OCTET_STRING_it);
return sym_ASN1_OCTET_STRING_it();
}
#define ASN1_item_new sym_ASN1_item_new
#define ASN1_item_free sym_ASN1_item_free
#define ASN1_ANY_it openssl_ASN1_ANY_it
#define ASN1_BIT_STRING_it openssl_ASN1_BIT_STRING_it
#define ASN1_BMPSTRING_it openssl_ASN1_BMPSTRING_it
#define ASN1_IA5STRING_it openssl_ASN1_IA5STRING_it
#define ASN1_OBJECT_it openssl_ASN1_OBJECT_it
#define ASN1_OCTET_STRING_it openssl_ASN1_OCTET_STRING_it
ASN1_SEQUENCE(SpcAttributeTypeAndOptionalValue) = {
ASN1_SIMPLE(SpcAttributeTypeAndOptionalValue, type, ASN1_OBJECT),
ASN1_OPT(SpcAttributeTypeAndOptionalValue, value, ASN1_ANY)
} static_ASN1_SEQUENCE_END(SpcAttributeTypeAndOptionalValue);
ASN1_SEQUENCE(AlgorithmIdentifier) = {
ASN1_SIMPLE(AlgorithmIdentifier, algorithm, ASN1_OBJECT),
ASN1_OPT(AlgorithmIdentifier, parameters, ASN1_ANY)
} static_ASN1_SEQUENCE_END(AlgorithmIdentifier)
ASN1_SEQUENCE(DigestInfo) = {
ASN1_SIMPLE(DigestInfo, digestAlgorithm, AlgorithmIdentifier),
ASN1_SIMPLE(DigestInfo, digest, ASN1_OCTET_STRING)
} static_ASN1_SEQUENCE_END(DigestInfo);
ASN1_SEQUENCE(SpcIndirectDataContent) = {
ASN1_SIMPLE(SpcIndirectDataContent, data, SpcAttributeTypeAndOptionalValue),
ASN1_SIMPLE(SpcIndirectDataContent, messageDigest, DigestInfo)
} static_ASN1_SEQUENCE_END(SpcIndirectDataContent);
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcIndirectDataContent);
ASN1_CHOICE(SpcString) = {
ASN1_IMP_OPT(SpcString, value.unicode, ASN1_BMPSTRING, 0),
ASN1_IMP_OPT(SpcString, value.ascii, ASN1_IA5STRING, 1)
} static_ASN1_CHOICE_END(SpcString);
ASN1_SEQUENCE(SpcSerializedObject) = {
ASN1_SIMPLE(SpcSerializedObject, classId, ASN1_OCTET_STRING),
ASN1_SIMPLE(SpcSerializedObject, serializedData, ASN1_OCTET_STRING)
} static_ASN1_SEQUENCE_END(SpcSerializedObject);
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)
} static_ASN1_CHOICE_END(SpcLink);
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcLink);
ASN1_SEQUENCE(SpcPeImageData) = {
ASN1_SIMPLE(SpcPeImageData, flags, ASN1_BIT_STRING),
ASN1_EXP_OPT(SpcPeImageData, file, SpcLink, 0)
} static_ASN1_SEQUENCE_END(SpcPeImageData)
IMPLEMENT_STATIC_ASN1_ALLOC_FUNCTIONS(SpcPeImageData);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcIndirectDataContent*, SpcIndirectDataContent_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcLink*, SpcLink_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SpcPeImageData*, SpcPeImageData_free, NULL);
int authenticode_spc_indirect_data_content_new(const void *digest, size_t digest_size, uint8_t **ret, size_t *ret_size) {
int r;
assert(digest);
assert(ret);
assert(ret_size);
r = dlopen_libcrypto(LOG_DEBUG);
if (r < 0)
return r;
/* See the authenticode spec https://aka.ms/AuthenticodeSpec for more information on the individual
* fields. */
/* <<<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_debug();
link->type = 2;
link->value.file = (SpcString*) sym_ASN1_item_new(ASN1_ITEM_rptr(SpcString));
if (!link->value.file)
return log_oom_debug();
link->value.file->type = 0;
link->value.file->value.unicode = sym_ASN1_BMPSTRING_new();
if (!link->value.file->value.unicode)
return log_oom_debug();
if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1 string");
_cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new();
if (!peid)
return log_oom_debug();
peid->file = TAKE_PTR(link);
_cleanup_free_ uint8_t *peidraw = NULL;
int peidrawsz = sym_ASN1_item_i2d((ASN1_VALUE*) peid, &peidraw, ASN1_ITEM_rptr(SpcPeImageData));
if (peidrawsz < 0)
return log_openssl_errors(LOG_DEBUG, "Failed to convert SpcPeImageData to BER");
_cleanup_(SpcIndirectDataContent_freep) SpcIndirectDataContent *idc = SpcIndirectDataContent_new();
if (!idc)
return log_oom_debug();
idc->data->value = sym_ASN1_TYPE_new();
if (!idc->data->value)
return log_oom_debug();
idc->data->value->type = V_ASN1_SEQUENCE;
idc->data->value->value.sequence = sym_ASN1_STRING_new();
if (!idc->data->value->value.sequence)
return log_oom_debug();
idc->data->type = sym_OBJ_txt2obj(SPC_PE_IMAGE_DATA_OBJID, /* no_name= */ 1);
if (!idc->data->type)
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcPeImageData object");
if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz))
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1_STRING data");
idc->messageDigest->digestAlgorithm->algorithm = sym_OBJ_nid2obj(NID_sha256);
if (!idc->messageDigest->digestAlgorithm->algorithm)
return log_openssl_errors(LOG_DEBUG, "Failed to get SHA256 object");
idc->messageDigest->digestAlgorithm->parameters = sym_ASN1_TYPE_new();
if (!idc->messageDigest->digestAlgorithm->parameters)
return log_oom_debug();
idc->messageDigest->digestAlgorithm->parameters->type = V_ASN1_NULL;
if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digest_size) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to set digest");
_cleanup_free_ uint8_t *idcraw = NULL;
int idcrawsz = sym_ASN1_item_i2d((ASN1_VALUE*) idc, &idcraw, ASN1_ITEM_rptr(SpcIndirectDataContent));
if (idcrawsz < 0)
return log_openssl_errors(LOG_DEBUG, "Failed to convert SpcIndirectDataContent to BER");
*ret = TAKE_PTR(idcraw);
*ret_size = (size_t) idcrawsz;
return 0;
}
static int asn1_timestamp(ASN1_TIME **ret) {
ASN1_TIME *time;
assert(ret);
usec_t epoch = parse_source_date_epoch();
if (epoch == USEC_INFINITY) {
time = sym_X509_gmtime_adj(NULL, 0);
if (!time)
return log_openssl_errors(LOG_DEBUG, "Failed to get current time");
} else {
time = sym_ASN1_TIME_set(NULL, (time_t) (epoch / USEC_PER_SEC));
if (!time)
return log_oom_debug();
}
*ret = time;
return 0;
}
int authenticode_pkcs7_new(
X509 *certificate,
EVP_PKEY *private_key,
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
PKCS7 **ret_p7,
PKCS7_SIGNER_INFO **ret_si) {
int r;
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, /* hash_algorithm= */ NULL, &p7, &si);
if (r < 0)
return log_debug_errno(r, "Failed to allocate PKCS#7 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 = (STACK_OF(X509_ALGOR)*) sym_OPENSSL_sk_new_null();
if (!smcap)
return log_oom_debug();
if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to add smimecap signed attribute to signer info");
if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to add content type signed attribute to signer info");
_cleanup_(ASN1_TIME_freep) ASN1_TIME *time = NULL;
r = asn1_timestamp(&time);
if (r < 0)
return r;
if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to add signing time signed attribute to signer info");
TAKE_PTR(time);
ASN1_OBJECT *idc = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!idc)
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcIndirectDataContent object");
if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to add signed attribute to pkcs7 signer info");
*ret_p7 = TAKE_PTR(p7);
*ret_si = TAKE_PTR(si);
return 0;
}
int authenticode_pkcs7_data_bio(PKCS7 *p7, const void *idc, size_t idc_size, BIO **ret) {
assert(p7);
assert(idc);
assert(ret);
_cleanup_(BIO_free_allp) BIO *bio = sym_PKCS7_dataInit(p7, NULL);
if (!bio)
return log_openssl_errors(LOG_DEBUG, "Failed to create PKCS7 data bio");
int tag, class;
long psz;
const uint8_t *p = idc;
/* ASN1_get_object() reports errors by setting the 0x80 bit in its return value. */
if (sym_ASN1_get_object(&p, &psz, &tag, &class, idc_size) & 0x80)
return log_openssl_errors(LOG_DEBUG, "Failed to parse ASN.1 object");
if (sym_BIO_write(bio, p, psz) < 0)
return log_openssl_errors(LOG_DEBUG, "Failed to write to PKCS7 data bio");
*ret = TAKE_PTR(bio);
return 0;
}
int authenticode_pkcs7_add_digest_attribute(BIO *data, PKCS7_SIGNER_INFO *si) {
assert(data);
assert(si);
BIO *mdbio = sym_BIO_find_type(data, BIO_TYPE_MD);
if (!mdbio)
return log_openssl_errors(LOG_DEBUG, "Failed to find digest bio");
EVP_MD_CTX *mdc;
if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0)
return log_openssl_errors(LOG_DEBUG, "Failed to get digest context from bio");
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned digestsz;
if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to get digest");
if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to add PKCS9 message digest signed attribute to signer info");
return 0;
}
int authenticode_pkcs7_finalize(PKCS7 *p7, const void *idc, size_t idc_size, uint8_t **ret, size_t *ret_size) {
assert(p7);
assert(idc);
assert(ret);
assert(ret_size);
_cleanup_(PKCS7_freep) PKCS7 *p7c = sym_PKCS7_new();
if (!p7c)
return log_oom_debug();
p7c->type = sym_OBJ_txt2obj(SPC_INDIRECT_DATA_OBJID, /* no_name= */ true);
if (!p7c->type)
return log_openssl_errors(LOG_DEBUG, "Failed to get SpcIndirectDataContent object");
p7c->d.other = sym_ASN1_TYPE_new();
if (!p7c->d.other)
return log_oom_debug();
p7c->d.other->type = V_ASN1_SEQUENCE;
p7c->d.other->value.sequence = sym_ASN1_STRING_new();
if (!p7c->d.other->value.sequence)
return log_oom_debug();
if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idc, idc_size) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to set ASN1 string");
if (sym_PKCS7_set_content(p7, p7c) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 data");
TAKE_PTR(p7c);
_cleanup_free_ uint8_t *sig = NULL;
int sigsz = sym_i2d_PKCS7(p7, &sig);
if (sigsz < 0)
return log_openssl_errors(LOG_DEBUG, "Failed to convert PKCS7 signature to DER");
*ret = TAKE_PTR(sig);
*ret_size = sigsz;
return 0;
}
int authenticode_sign_pe(int fd, X509 *certificate, EVP_PKEY *private_key) {
int r;
assert(fd >= 0);
assert(certificate);
assert(private_key);
r = dlopen_libcrypto(LOG_DEBUG);
if (r < 0)
return r;
_cleanup_free_ void *pehash = NULL;
size_t pehashsz;
r = pe_hash(fd, sym_EVP_sha256(), &pehash, &pehashsz);
if (r < 0)
return log_debug_errno(r, "Failed to hash PE binary: %m");
_cleanup_free_ uint8_t *idc = NULL;
size_t idcsz = 0; /* avoid false maybe-uninitialized warning */
r = authenticode_spc_indirect_data_content_new(pehash, pehashsz, &idc, &idcsz);
if (r < 0)
return r;
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = authenticode_pkcs7_new(certificate, private_key, /* signed_attributes= */ NULL, &p7, &si);
if (r < 0)
return r;
_cleanup_(BIO_free_allp) BIO *bio = NULL;
r = authenticode_pkcs7_data_bio(p7, idc, idcsz, &bio);
if (r < 0)
return r;
if (sym_PKCS7_dataFinal(p7, bio) == 0)
return log_openssl_errors(LOG_DEBUG, "Failed to sign data");
_cleanup_free_ uint8_t *sig = NULL;
size_t sigsz;
r = authenticode_pkcs7_finalize(p7, idc, idcsz, &sig, &sigsz);
if (r < 0)
return r;
return pe_append_signature(fd, sig, sigsz);
}
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL
# error "missing libopenssl_cflags in meson dependency."
#endif
#include "crypto-util.h"
#include "forward.h"
int authenticode_spc_indirect_data_content_new(const void *digest, size_t digest_size, uint8_t **ret, size_t *ret_size);
int authenticode_pkcs7_new(
X509 *certificate,
EVP_PKEY *private_key,
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
PKCS7 **ret_p7,
PKCS7_SIGNER_INFO **ret_si);
int authenticode_pkcs7_data_bio(PKCS7 *p7, const void *idc, size_t idc_size, BIO **ret);
int authenticode_pkcs7_add_digest_attribute(BIO *data, PKCS7_SIGNER_INFO *si);
int authenticode_pkcs7_finalize(PKCS7 *p7, const void *idc, size_t idc_size, uint8_t **ret, size_t *ret_size);
int authenticode_sign_pe(int fd, X509 *certificate, EVP_PKEY *private_key);
+4
View File
@@ -325,6 +325,10 @@ if conf.get('ENABLE_REPORT') == 1
endif
endif
if conf.get('HAVE_OPENSSL') == 1
shared_sources += files('authenticode.c')
endif
generate_ip_protocol_list = files('generate-ip-protocol-list.sh')
ip_protocol_list_txt = custom_target(
input : [generate_ip_protocol_list, ipproto_sources],
+1
View File
@@ -487,6 +487,7 @@ executables += [
},
test_template + {
'sources' : files('test-pe-writer.c'),
'dependencies' : libopenssl_cflags,
},
test_template + {
'sources' : files('test-pkcs7-util.c'),
+46
View File
@@ -12,6 +12,11 @@
#include "string-util.h"
#include "tests.h"
#if HAVE_OPENSSL
# include "authenticode.h"
# include "crypto-util.h"
#endif
#define PE_OFFSET 0x40U
#define OPTIONAL_HEADER_SIZE 0xf0U
#define SECTION_TABLE_OFFSET (PE_OFFSET + offsetof(PeHeader, optional) + OPTIONAL_HEADER_SIZE)
@@ -601,4 +606,45 @@ TEST(signature_unaligned_input) {
assert_zero(fd, STUB_SIZE + 3, 5);
}
#if HAVE_OPENSSL
TEST(authenticode_sign_pe) {
_cleanup_(EVP_PKEY_freep) EVP_PKEY *key = NULL;
_cleanup_(X509_freep) X509 *cert = NULL;
if (dlopen_libcrypto(LOG_DEBUG) < 0)
return (void) log_tests_skipped("libcrypto not available");
ASSERT_OK(rsa_pkey_new(2048, &key));
ASSERT_OK(x509_new_self_signed(key, "systemd test", 30, &cert));
_cleanup_free_ uint8_t *stub = build_stub(STUB_SIZE, 0x400);
_cleanup_close_ int fd = ASSERT_OK(memfd_new("stub"));
ASSERT_OK_EQ_ERRNO(pwrite(fd, stub, STUB_SIZE, 0), (ssize_t) STUB_SIZE);
uint64_t unsigned_size = ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END));
_cleanup_free_ void *hash = NULL, *signed_hash = NULL;
size_t hash_size, signed_hash_size;
ASSERT_OK(pe_hash(fd, sym_EVP_sha256(), &hash, &hash_size));
ASSERT_OK(authenticode_sign_pe(fd, cert, key));
_cleanup_free_ PeHeader *pe_header = NULL;
ASSERT_OK(pe_load_headers_and_sections(fd, &pe_header, /* ret_sections= */ NULL));
ASSERT_TRUE(pe_header_is_signed(pe_header));
const IMAGE_DATA_DIRECTORY *certificate_table =
pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
ASSERT_EQ(le32toh(certificate_table->VirtualAddress), unsigned_size);
ASSERT_OK_EQ_ERRNO(lseek(fd, 0, SEEK_END), (off_t) (unsigned_size + le32toh(certificate_table->Size)));
uint32_t checksum;
ASSERT_OK(pe_checksum(fd, &checksum));
ASSERT_EQ(le32toh(pe_header->optional.CheckSum), checksum);
/* The Authenticode hash does not cover the certificate table, so signing does not change it. */
ASSERT_OK(pe_hash(fd, sym_EVP_sha256(), &signed_hash, &signed_hash_size));
ASSERT_EQ(memcmp_nn(signed_hash, signed_hash_size, hash, hash_size), 0);
}
#endif
DEFINE_TEST_MAIN(LOG_DEBUG);