[PATCH v5 07/10] hornet: Introduce gen_sig

Fan Wu wufan at kernel.org
Tue Apr 21 00:18:56 UTC 2026


On Mon, Apr 20, 2026 at 2:27 PM Blaise Boscaccy
<bboscaccy at linux.microsoft.com> wrote:
>
> This introduces the gen_sig tool. It creates a pkcs#7 signature of a
> data payload. Additionally it appends a signed attribute containing a
> set of hashes.
>
> Typical usage is to provide a payload containing the light skeleton
> ebpf syscall program binary and it's associated maps, which can be
> extracted from the auto-generated skeleton header.
>
> Signed-off-by: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> ---
>  scripts/Makefile            |   1 +
>  scripts/hornet/Makefile     |   5 +
>  scripts/hornet/gen_sig.c    | 392 ++++++++++++++++++++++++++++++++++++
>  scripts/hornet/write-sig.sh |  27 +++
>  4 files changed, 425 insertions(+)
>  create mode 100644 scripts/hornet/Makefile
>  create mode 100644 scripts/hornet/gen_sig.c
>  create mode 100755 scripts/hornet/write-sig.sh
>
> diff --git a/scripts/Makefile b/scripts/Makefile
> index 0941e5ce7b575..dea8ab91bbe4e 100644
> --- a/scripts/Makefile
> +++ b/scripts/Makefile
> @@ -63,6 +63,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms
>  subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
>  subdir-$(CONFIG_SECURITY_SELINUX) += selinux
>  subdir-$(CONFIG_SECURITY_IPE) += ipe
> +subdir-$(CONFIG_SECURITY_HORNET) += hornet
>
>  # Let clean descend into subdirs
>  subdir-        += basic dtc gdb kconfig mod
> diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile
> new file mode 100644
> index 0000000000000..3ee41e5e9a9ff
> --- /dev/null
> +++ b/scripts/hornet/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +hostprogs-always-y     := gen_sig
> +
> +HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
> +HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
> diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
> new file mode 100644
> index 0000000000000..f966516ebc99b
> --- /dev/null
> +++ b/scripts/hornet/gen_sig.c
> @@ -0,0 +1,392 @@
> +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
> + *
> + * Generate a signature for an eBPF program along with appending
> + * map hashes as signed attributes
> + *
> + * Copyright © 2025      Microsoft Corporation.
> + *
> + * Authors: Blaise Boscaccy <bboscaccy at linux.microsoft.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public License
> + * as published by the Free Software Foundation; either version 2.1
> + * of the licence, or (at your option) any later version.
> + */
> +
> +#define _GNU_SOURCE
> +#include <stdio.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <err.h>
> +#include <getopt.h>
> +
> +#include <openssl/cms.h>
> +#include <openssl/err.h>
> +#include <openssl/evp.h>
> +#include <openssl/pkcs7.h>
> +#include <openssl/x509.h>
> +#include <openssl/pem.h>
> +#include <openssl/objects.h>
> +#include <openssl/asn1.h>
> +#include <openssl/asn1t.h>
> +#include <openssl/opensslv.h>
> +#include <openssl/bio.h>
> +#include <openssl/stack.h>
> +
> +#if OPENSSL_VERSION_MAJOR >= 3
> +# define USE_PKCS11_PROVIDER
> +# include <openssl/provider.h>
> +# include <openssl/store.h>
> +#else
> +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
> +#  define USE_PKCS11_ENGINE
> +#  include <openssl/engine.h>
> +# endif
> +#endif
> +#include "../ssl-common.h"
> +
> +#define SHA256_LEN 32
> +#define BUF_SIZE   (1 << 15) // 32 KiB
> +#define MAX_HASHES 64
> +
> +struct hash_spec {
> +       char *file;
> +       int index;
> +};
> +
> +typedef struct {
> +       ASN1_INTEGER *index;
> +       ASN1_OCTET_STRING *hash;
> +
> +} HORNET_MAP;
> +
> +DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
> +ASN1_SEQUENCE(HORNET_MAP) = {
> +       ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
> +       ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
> +} ASN1_SEQUENCE_END(HORNET_MAP);
> +
> +IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP)
> +
> +DEFINE_STACK_OF(HORNET_MAP)
> +
> +typedef struct {
> +       STACK_OF(HORNET_MAP) * maps;
> +} MAP_SET;
> +
> +DECLARE_ASN1_FUNCTIONS(MAP_SET)
> +ASN1_SEQUENCE(MAP_SET) = {
> +       ASN1_SET_OF(MAP_SET, maps, HORNET_MAP)
> +} ASN1_SEQUENCE_END(MAP_SET);
> +
> +IMPLEMENT_ASN1_FUNCTIONS(MAP_SET)
> +
> +#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \
> +               exit(EXIT_FAILURE); } while (0)
> +
> +static BIO *bio_open_wr(const char *path)
> +{
> +       BIO *b = BIO_new_file(path, "wb");
> +
> +       if (!b) {
> +               perror(path);
> +               ERR_print_errors_fp(stderr);
> +               exit(EXIT_FAILURE);
> +       }
> +       return b;
> +}
> +
> +static void usage(const char *prog)
> +{
> +       fprintf(stderr,
> +               "Usage:\n"
> +               "  %s --data content.bin --cert signer.crt --key signer.key [-pass pass]\n"
> +               "     --out newsig.p7b \n"
> +               "     --add FILE:index [--add FILE:index ...]\n",
> +               prog);
> +}
> +
> +static const char *key_pass;
> +
> +static int pem_pw_cb(char *buf, int len, int w, void *v)
> +{
> +       int pwlen;
> +
> +       if (!key_pass)
> +               return -1;
> +
> +       pwlen = strlen(key_pass);
> +       if (pwlen >= len)
> +               return -1;
> +
> +       strcpy(buf, key_pass);
> +
> +       key_pass = NULL;
> +
> +       return pwlen;
> +}
> +
> +static EVP_PKEY *read_private_key(const char *private_key_name)
> +{
> +       EVP_PKEY *private_key;
> +       BIO *b;
> +
> +       b = BIO_new_file(private_key_name, "rb");
> +       ERR(!b, "%s", private_key_name);
> +       private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb,
> +                                             NULL);
> +       ERR(!private_key, "%s", private_key_name);
> +       BIO_free(b);
> +
> +       return private_key;
> +}
> +
> +static X509 *read_x509(const char *x509_name)
> +{
> +       unsigned char buf[2];
> +       X509 *x509;
> +       BIO *b;
> +       int n;
> +
> +       b = BIO_new_file(x509_name, "rb");
> +       ERR(!b, "%s", x509_name);
> +
> +       /* Look at the first two bytes of the file to determine the encoding */
> +       n = BIO_read(b, buf, 2);
> +       if (n != 2) {
> +               if (BIO_should_retry(b)) {
> +                       fprintf(stderr, "%s: Read wanted retry\n", x509_name);
> +                       exit(1);
> +               }
> +               if (n >= 0) {
> +                       fprintf(stderr, "%s: Short read\n", x509_name);
> +                       exit(1);
> +               }
> +               ERR(1, "%s", x509_name);
> +       }
> +
> +       ERR(BIO_reset(b) != 0, "%s", x509_name);
> +
> +       if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84)
> +               /* Assume raw DER encoded X.509 */
> +               x509 = d2i_X509_bio(b, NULL);
> +       else
> +               /* Assume PEM encoded X.509 */
> +               x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
> +
> +       BIO_free(b);
> +       ERR(!x509, "%s", x509_name);
> +
> +       return x509;
> +}
> +
> +static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len)
> +{
> +       FILE *f;
> +       int rc;
> +       EVP_MD_CTX *ctx;
> +       unsigned char buf[BUF_SIZE];
> +       size_t n;
> +       unsigned int mdlen = 0;
> +
> +       if (!path || !out)
> +               return -1;
> +
> +       f = fopen(path, "rb");
> +       if (!f) {
> +               perror("fopen");
> +               return -2;
> +       }
> +
> +       ERR_load_crypto_strings();
> +
> +       rc = -3;
> +       ctx = EVP_MD_CTX_new();
> +       if (!ctx) {
> +               rc = -4;
> +               goto done;
> +       }
> +
> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L
> +       if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) {
> +               rc = -5;
> +               goto done;
> +       }
> +#else
> +       if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) {
> +               rc = -5;
> +               goto done;
> +       }
> +#endif
> +       while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
> +               if (EVP_DigestUpdate(ctx, buf, n) != 1) {
> +                       rc = -6;
> +                       goto done;
> +               }
> +       }
> +       if (ferror(f)) {
> +               rc = -7;
> +               goto done;
> +       }
> +
> +       if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) {
> +               rc = -8;
> +               goto done;
> +       }
> +       if (mdlen != SHA256_LEN) {
> +               rc = -9;
> +               goto done;
> +       }
> +
> +       if (out_len)
> +               *out_len = mdlen;
> +       rc = 0;
> +
> +done:
> +       EVP_MD_CTX_free(ctx);
> +       fclose(f);
> +       ERR_free_strings();
> +       return rc;
> +}
> +
> +static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
> +{
> +       HORNET_MAP *map = NULL;
> +
> +       map = HORNET_MAP_new();
> +       ASN1_INTEGER_set(map->index, index);
> +       ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
> +       sk_HORNET_MAP_push(set->maps, map);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +       const char *cert_path = NULL;
> +       const char *key_path = NULL;
> +       const char *data_path = NULL;
> +       const char *out_path = NULL;
> +
> +       X509 *signer;
> +       EVP_PKEY *pkey;
> +       BIO *data_in;
> +       CMS_ContentInfo *cms_out;
> +       struct hash_spec hashes[MAX_HASHES];
> +       int hash_count = 0;
> +       int flags;
> +       CMS_SignerInfo *si;
> +       MAP_SET *set;
> +       unsigned char hash_buffer[SHA256_LEN];
> +       unsigned int hash_len;
> +       ASN1_OBJECT *oid;
> +       unsigned char *der = NULL;
> +       int der_len;
> +       int err;
> +       BIO *b_out;
> +       int i;
> +       char opt;
> +
> +       const char *short_opts = "C:K:P:O:A:Sh";
> +
> +       static const struct option long_opts[] = {
> +               {"cert", required_argument, 0, 'C'},
> +               {"key",  required_argument, 0, 'K'},
> +               {"pass",  required_argument, 0, 'P'},
> +               {"out",  required_argument, 0, 'O'},
> +               {"data",  required_argument, 0, 'D'},
> +               {"add",  required_argument, 0, 'A'},
> +               {"help",    no_argument,       0, 'h'},
> +               {0, 0, 0, 0}
> +       };
> +
> +       while ((opt = getopt_long_only(argc, argv, short_opts, long_opts, NULL)) != -1) {
> +               switch (opt) {
> +               case 'C':
> +                       cert_path = optarg;
> +                       break;
> +               case 'K':
> +                       key_path = optarg;
> +                       break;
> +               case 'P':
> +                       key_pass = optarg;
> +                       break;
> +               case 'O':
> +                       out_path = optarg;
> +                       break;
> +               case 'D':
> +                       data_path = optarg;
> +                       break;
> +               case 'A':
> +                       if (strchr(optarg, ':')) {
> +                               hashes[hash_count].file = strsep(&optarg, ":");
> +                               hashes[hash_count].index = atoi(optarg);
> +                               hash_count++;

Should the hash_count be compared to MAX_HASHES here?

> +                       } else {
> +                               usage(argv[0]);
> +                               return EXIT_FAILURE;
> +                       }
> +               }
> +       }
> +
> +       if (!cert_path || !key_path || !out_path || !data_path) {
> +               usage(argv[0]);
> +               return EXIT_FAILURE;
> +       }
> +
> +       OpenSSL_add_all_algorithms();
> +       ERR_load_crypto_strings();
> +
> +       signer = read_x509(cert_path);
> +       ERR(!signer, "Load cert failed");
> +
> +       pkey = read_private_key(key_path);
> +       ERR(!pkey, "Load key failed");
> +
> +       data_in = BIO_new_file(data_path, "rb");
> +       ERR(!data_in, "Load data failed");
> +
> +       cms_out = CMS_sign(NULL, NULL, NULL, NULL,
> +                          CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED);
> +       ERR(!cms_out, "create cms failed");
> +
> +       flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED;
> +
> +       si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags);
> +       ERR(!si, "add signer failed");
> +
> +       set = MAP_SET_new();
> +       set->maps = sk_HORNET_MAP_new_null();
> +
> +       for (i = 0; i < hash_count; i++) {
> +               sha256(hashes[i].file, hash_buffer, &hash_len);

Can sha256 fail here? In that case the program should stop here and
report errors.

-Fan

> +               add_hash(set, hash_buffer, hash_len, hashes[i].index);
> +       }
> +
> +       oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
> +       if (!oid) {
> +               ERR_print_errors_fp(stderr);
> +               DIE("create oid failed");
> +       }
> +
> +       der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET));
> +       CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len);
> +
> +       err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY);
> +       ERR(!err, "cms final failed");
> +
> +       OPENSSL_free(der);
> +       MAP_SET_free(set);
> +
> +       b_out = bio_open_wr(out_path);
> +       ERR(!b_out, "opening output path failed");
> +
> +       i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
> +
> +       BIO_free(data_in);
> +       BIO_free(b_out);
> +       EVP_cleanup();
> +       ERR_free_strings();
> +       return 0;
> +}
> diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
> new file mode 100755
> index 0000000000000..7eaabe3bab9aa
> --- /dev/null
> +++ b/scripts/hornet/write-sig.sh
> @@ -0,0 +1,27 @@
> +#!/bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Copyright (c) 2025 Microsoft Corporation
> +#
> +# This program is free software; you can redistribute it and/or
> +# modify it under the terms of version 2 of the GNU General Public
> +# License as published by the Free Software Foundation.
> +
> +function usage() {
> +    echo "Sample for rewriting an autogenerated eBPF lskel headers"
> +    echo "with a new signature"
> +    echo ""
> +    echo "USAGE: header_file sig"
> +    exit
> +}
> +
> +ARGC=$#
> +
> +EXPECTED_ARGS=2
> +
> +if [ $ARGC -ne $EXPECTED_ARGS ] ; then
> +    usage
> +else
> +    SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g')
> +    sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1
> +fi
> --
> 2.53.0
>



More information about the Linux-security-module-archive mailing list