diff --git a/Makefile.am b/Makefile.am index a3e6ef2..699f7f0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ -AM_CFLAGS = -Wall -Wextra +AM_CFLAGS = -Wall -Wextra --std=gnu99 -bin_PROGRAMS = sbsign sbverify sbattach +bin_PROGRAMS = sbsign sbverify sbattach sbvarsign coff_headers = coff/external.h coff/pe.h coff/i386.h coff/x86_64.h @@ -21,6 +21,12 @@ sbattach_SOURCES = sbattach.c $(common_SOURCES) sbattach_LDADD = $(common_LDADD) sbattach_CFLAGS = $(AM_CFLAGS) $(common_CFLAGS) +sbvarsign_SOURCES = sbvarsign.c +sbvarsign_LDADD = $(common_LDADD) $(uuid_LIBS) +sbvarsign_CPPFLAGS = $(EFI_CPPFLAGS) +sbvarsign_CFLAGS = $(AM_CFLAGS) $(uuid_CFLAGS) $(common_CFLAGS) + + man1_MANS = docs/sbsign.1 docs/sbverify.1 docs/sbattach.1 EXTRA_DIST = docs/sbsign.1.in docs/sbverify.1.in docs/sbattach.1.in diff --git a/autogen.sh b/autogen.sh index 8083fa3..32ea6f6 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,6 +1,6 @@ #!/bin/bash -ccan_modules="talloc read_write_all build_assert" +ccan_modules="talloc read_write_all build_assert array_size" # Add ccan upstream sources if [ ! -e lib/ccan.git/Makefile ] diff --git a/configure.ac b/configure.ac index a452989..0a7f2db 100644 --- a/configure.ac +++ b/configure.ac @@ -59,5 +59,19 @@ PKG_CHECK_MODULES(libcrypto, libcrypto, [], AC_MSG_ERROR([libcrypto (from the OpenSSL package) is required])) +PKG_CHECK_MODULES(uuid, uuid, + [], + AC_MSG_ERROR([libuuid (from the uuid package) is required])) + +dnl gnu-efi headers require extra include dirs +EFI_ARCH=$(uname -m) +EFI_CPPFLAGS="-I/usr/include/efi -I/usr/include/efi/$EFI_ARCH \ + -DEFI_FUNCTION_WRAPPER" +CPPFLAGS_save="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $EFI_CPPFLAGS" +AC_CHECK_HEADERS([efi.h], [], [], $EFI_INCLUDES) +CPPFLAGS="$CPPFLAGS_save" +AC_SUBST(EFI_CPPFLAGS, $EFI_CPPFLAGS) + AC_CONFIG_FILES([Makefile lib/ccan/Makefile tests/Makefile]) AC_OUTPUT diff --git a/efi-varauth.h b/efi-varauth.h new file mode 100644 index 0000000..742e0e0 --- /dev/null +++ b/efi-varauth.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 Jeremy Kerr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the OpenSSL + * library under certain conditions as described in each individual source file, + * and distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + */ +#ifndef EFI_VARAUTH_H +#define EFI_VARAUTH_H + +#include + +#define EFI_CERT_TYPE_PKCS7_GUID \ + {0x4aafd29d, 0x68df, 0x49ee, \ + {0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7}} + +typedef struct { + UINT32 dwLength; + UINT16 wRevision; + UINT16 wCertificateType; + UINT8 bCertificate[]; +} WIN_CERTIFICATE; + +typedef struct { + WIN_CERTIFICATE Hdr; + EFI_GUID CertType; + UINT8 CertData[]; +} WIN_CERTIFICATE_UEFI_GUID; + +typedef struct { + EFI_TIME TimeStamp; + WIN_CERTIFICATE_UEFI_GUID AuthInfo; +} EFI_VARIABLE_AUTHENTICATION_2; + +#endif /* EFI_VARAUTH_H */ + diff --git a/sbvarsign.c b/sbvarsign.c new file mode 100644 index 0000000..ab4dabf --- /dev/null +++ b/sbvarsign.c @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2012 Jeremy Kerr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the OpenSSL + * library under certain conditions as described in each individual source file, + * and distribute linked combinations including the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "efi-varauth.h" + +static const char *toolname = "sbvarsign"; + + +struct varsign_context { + const char *infilename; + const char *outfilename; + + uint8_t *data; + unsigned int data_len; + + CHAR16 *var_name; + int var_name_bytes; + EFI_GUID var_guid; + uint32_t var_attrs; + + EVP_PKEY *key; + X509 *cert; + + EFI_VARIABLE_AUTHENTICATION_2 *auth_descriptor; + int auth_descriptor_len; + EFI_TIME timestamp; + + int verbose; +}; + +struct attr { + const char *name; + int value; +}; + +static struct attr attrs[] = { + { "NON_VOLATILE", 0x0001 }, + { "BOOTSERVICE_ACCESS", 0x0002 }, + { "RUNTIME_ACCESS", 0x0004 }, + { "TIME_BASED_AUTHENTICATED_WRITE_ACCESS", 0x0020 }, + { "APPEND_WRITE", 0x0040 }, +}; + +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x0020 + +static uint32_t attr_invalid = 0xffffffffu; +static const char *attr_prefix = "EFI_VARIABLE_"; + +static const EFI_GUID default_guid = EFI_GLOBAL_VARIABLE; +static const EFI_GUID cert_pkcs7_guid = EFI_CERT_TYPE_PKCS7_GUID; + +static void set_default_outfilename(struct varsign_context *ctx) +{ + const char *extension = "signed"; + + ctx->outfilename = talloc_asprintf(ctx, "%s.%s", + ctx->infilename, extension); +} + +static uint32_t parse_single_attr(const char *attr_str) +{ + unsigned int i; + + /* skip standard prefix, if present */ + if (!strncmp(attr_str, attr_prefix, strlen(attr_prefix))) + attr_str += strlen(attr_prefix); + + for (i = 0; i < ARRAY_SIZE(attrs); i++) { + if (!strcmp(attr_str, attrs[i].name)) + return attrs[i].value; + } + + return attr_invalid; +} + +static uint32_t parse_attrs(const char *attrs_str) +{ + uint32_t attr, attrs_val; + const char *attr_str; + char *str; + + /* we always need E_V_T_B_A_W_A */ + attrs_val = EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + + if (!attrs_str || !attrs_str[0]) + return attrs_val; + + str = strdup(attrs_str); + + for (attr_str = strtok(str, ","); attr_str; + attr_str = strtok(NULL, ",")) { + + attr = parse_single_attr(attr_str); + if (attr == attr_invalid) { + fprintf(stderr, "Invalid attribute string %s\n", + attr_str); + return attr_invalid; + } + + attrs_val |= attr; + } + + return attrs_val; +} + +static int set_varname(struct varsign_context *ctx, const char *str) +{ + CHAR16 *wstr; + int i, len; + + len = strlen(str); + + wstr = talloc_array(ctx, CHAR16, len + 1); + + for (i = 0; i < len; i++) + wstr[i] = str[i]; + + wstr[i] = '\0'; + + ctx->var_name = wstr; + ctx->var_name_bytes = i * sizeof(CHAR16); + + return 0; +} + +static int parse_guid(const char *str, EFI_GUID *guid) +{ + uuid_t uuid; + + if (uuid_parse(str, uuid)) + return -1; + + /* convert to an EFI_GUID */ + guid->Data1 = uuid[0] << 24 | uuid[1] << 16 | uuid[2] << 8 | uuid[3]; + guid->Data2 = uuid[4] << 8 | uuid[5]; + guid->Data3 = uuid[6] << 8 | uuid[7]; + memcpy(guid->Data4, &uuid[8], sizeof(guid->Data4)); + + return 0; +} + +static int read_var_data(struct varsign_context *ctx) +{ + struct stat statbuf; + int rc, fd = -1; + + fd = open(ctx->infilename, O_RDONLY); + if (fd < 0) { + perror("open"); + goto err; + } + + rc = fstat(fd, &statbuf); + if (rc) { + perror("fstat"); + goto err; + } + + ctx->data_len = statbuf.st_size; + ctx->data = talloc_size(ctx, ctx->data_len); + + if (!ctx->data) { + perror("talloc"); + goto err; + } + + if (!read_all(fd, ctx->data, ctx->data_len)) { + perror("read_all"); + goto err; + } + + close(fd); + return 0; + +err: + if (fd > 0) + close(fd); + ctx->data_len = 0; + talloc_free(ctx->data); + ctx->data = NULL; + fprintf(stderr, "Can't read variable data from file %s\n", + ctx->infilename); + return -1; +} + +static int set_timestamp(EFI_TIME *timestamp) +{ + struct tm *tm; + time_t t; + + time(&t); + + tm = gmtime(&t); + if (!tm) { + perror("gmtime"); + return -1; + } + + /* copy to our EFI-specific time structure. Other fields (Nanosecond, + * TimeZone, Daylight and Pad) are defined to be zero */ + memset(timestamp, 0, sizeof(timestamp)); + timestamp->Year = tm->tm_year; + timestamp->Month = tm->tm_mon; + timestamp->Day = tm->tm_mday; + timestamp->Hour = tm->tm_hour; + timestamp->Minute = tm->tm_min; + timestamp->Second = tm->tm_sec; + + return 0; +} + +static int load_key(struct varsign_context *ctx, const char *filename) +{ + BIO *bio; + + bio = BIO_new_file(filename, "r"); + if (!bio) + goto err; + + ctx->key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (!ctx->key) + goto err; + + BIO_free_all(bio); + + return 0; + +err: + if (bio) + BIO_free_all(bio); + fprintf(stderr, "Can't load key from file '%s'\n", filename); + ERR_print_errors_fp(stderr); + return -1; +} + +static int load_cert(struct varsign_context *ctx, const char *filename) +{ + BIO *bio; + + bio = BIO_new_file(filename, "r"); + if (!bio) + goto err; + + ctx->cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (!ctx->cert) + goto err; + + BIO_free_all(bio); + + return 0; + +err: + if (bio) + BIO_free_all(bio); + fprintf(stderr, "Can't load certificate from file '%s'\n", filename); + ERR_print_errors_fp(stderr); + return -1; + +} + +static int add_auth_descriptor(struct varsign_context *ctx) +{ + EFI_VARIABLE_AUTHENTICATION_2 *auth; + int rc, len, flags; + EFI_TIME timestamp; + const EVP_MD *md; + BIO *data_bio; + uint8_t *tmp; + PKCS7 *p7; + + if (set_timestamp(×tamp)) + return -1; + + /* create a BIO for our variable data, containing: + * * Variablename (not including trailing nul) + * * VendorGUID + * * Attributes + * * TimeStamp + * * Data + */ + data_bio = BIO_new(BIO_s_mem()); + BIO_write(data_bio, ctx->var_name, ctx->var_name_bytes); + BIO_write(data_bio, &ctx->var_guid, sizeof(ctx->var_guid)); + BIO_write(data_bio, &ctx->var_attrs, sizeof(ctx->var_attrs)); + BIO_write(data_bio, ×tamp, sizeof(timestamp)); + BIO_write(data_bio, &ctx->data, ctx->data_len); + + md = EVP_get_digestbyname("SHA256"); + + p7 = PKCS7_new(); + flags = PKCS7_BINARY | PKCS7_DETACHED | PKCS7_NOSMIMECAP;; + PKCS7_set_type(p7, NID_pkcs7_signed); + + PKCS7_content_new(p7, NID_pkcs7_data); + + PKCS7_sign_add_signer(p7, ctx->cert, ctx->key, md, flags); + + PKCS7_set_detached(p7, 1); + + rc = PKCS7_final(p7, data_bio, flags); + if (!rc) { + fprintf(stderr, "Error signing variable data\n"); + ERR_print_errors_fp(stderr); + BIO_free_all(data_bio); + return -1; + } + + len = i2d_PKCS7(p7, NULL); + + + /* set up our auth descriptor */ + auth = talloc_size(ctx, sizeof(*auth) + len); + + auth->TimeStamp = timestamp; + auth->AuthInfo.Hdr.dwLength = len + sizeof(EFI_GUID); + auth->AuthInfo.Hdr.wRevision = 0x0200; + auth->AuthInfo.Hdr.wCertificateType = 0x0EF1; + auth->AuthInfo.CertType = cert_pkcs7_guid; + tmp = auth->AuthInfo.CertData; + i2d_PKCS7(p7, &tmp); + + ctx->auth_descriptor = auth; + ctx->auth_descriptor_len = sizeof(*auth) + len; + + BIO_free_all(data_bio); + + return 0; +} + +int write_signed(struct varsign_context *ctx, int include_attrs) +{ + int fd, rc; + + fd = open(ctx->outfilename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror("open"); + goto err; + } + + /* For some uses (eg, writing to the efivars filesystem), we may + * want to prefix the signed variable with four bytes of attribute + * data + */ + if (include_attrs) { + rc = write_all(fd, &ctx->var_attrs, sizeof(ctx->var_attrs)); + if (!rc) { + perror("write_all"); + goto err; + } + } + + /* Write the authentication descriptor */ + rc = write_all(fd, ctx->auth_descriptor, ctx->auth_descriptor_len); + if (!rc) { + perror("write_all"); + goto err; + } + + /* ... and the variable data itself */ + rc = write_all(fd, ctx->data, ctx->data_len); + if (!rc) { + perror("write_all"); + goto err; + } + + if (ctx->verbose) { + size_t i = 0; + + printf("Wrote signed data:\n"); + if (include_attrs) { + i = sizeof(ctx->var_attrs); + printf(" [%04zx:%04zx] attrs\n", 0l, i); + } + + printf(" [%04zx:%04x] authentication descriptor\n", + i, ctx->auth_descriptor_len); + + printf(" [%04zx:%04zx] EFI_VAR_AUTH_2 header\n", + i, + sizeof(EFI_VARIABLE_AUTHENTICATION_2)); + + printf(" [%04zx:%04zx] WIN_CERT_UEFI_GUID header\n", + i + offsetof(EFI_VARIABLE_AUTHENTICATION_2, + AuthInfo), + sizeof(WIN_CERTIFICATE_UEFI_GUID)); + + printf(" [%04zx:%04zx] WIN_CERT header\n", + i + offsetof(EFI_VARIABLE_AUTHENTICATION_2, + AuthInfo.Hdr), + sizeof(WIN_CERTIFICATE)); + + printf(" [%04zx:%04zx] pkcs7 data\n", + i + offsetof(EFI_VARIABLE_AUTHENTICATION_2, + AuthInfo.CertData), + ctx->auth_descriptor_len - + sizeof(EFI_VARIABLE_AUTHENTICATION_2)); + + i += ctx->auth_descriptor_len; + + printf(" [%04zx:%04zx] variable data\n", + i, i + ctx->data_len); + } + + close(fd); + return 0; + +err: + fprintf(stderr, "Can't write signed data to file '%s'\n", + ctx->outfilename); + if (fd >= 0) + close(fd); + return -1; + +} + +static struct option options[] = { + { "output", required_argument, NULL, 'o' }, + { "guid", required_argument, NULL, 'g' }, + { "attrs", required_argument, NULL, 'a' }, + { "key", required_argument, NULL, 'k' }, + { "cert", required_argument, NULL, 'c' }, + { "include-attrs", no_argument, NULL, 'i' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +void usage(void) +{ + unsigned int i; + + printf("Usage: %s [options] --key --cert " + " \n" + "Sign a blob of data for use in SetVariable().\n\n" + "Options:\n" + "\t--key signing key (PEM-encoded RSA " + "private key)\n" + "\t--cert certificate (x509 certificate)\n" + "\t--include-attrs include attrs at beginning of output file\n" + "\t--guid EFI GUID for the variable. If omitted,\n" + "\t EFI_GLOBAL_VARIABLE will be used\n" + "\t--attr variable attributes. One or more of:\n", + toolname); + + for (i = 0; i < ARRAY_SIZE(attrs); i++) + printf("\t %s\n", attrs[i].name); + + printf("\t Separate multiple attrs with a comma\n" + "\t--output write signed data to \n" + "\t (default .signed)\n"); +} + +static void version(void) +{ + printf("%s %s\n", toolname, VERSION); +} + +int main(int argc, char **argv) +{ + const char *keyfilename, *certfilename; + const char *guid_str, *attr_str; + struct varsign_context *ctx; + bool include_attrs; + int c; + + ctx = talloc_zero(NULL, struct varsign_context); + + keyfilename = NULL; + certfilename = NULL; + guid_str = NULL; + attr_str= NULL; + include_attrs = false; + + for (;;) { + int idx; + c = getopt_long(argc, argv, "o:g:a:k:c:ivVh", options, &idx); + if (c == -1) + break; + + switch (c) { + case 'o': + ctx->outfilename = optarg; + break; + case 'g': + guid_str = optarg; + break; + case 'a': + attr_str = optarg; + break; + case 'k': + keyfilename = optarg; + break; + case 'c': + certfilename = optarg; + break; + case 'i': + include_attrs = true; + break; + case 'v': + ctx->verbose = 1; + break; + case 'V': + version(); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + } + } + + if (argc != optind + 2) { + usage(); + return EXIT_FAILURE; + } + + if (!keyfilename) { + fprintf(stderr, "No signing key specified\n"); + return EXIT_FAILURE; + } + + if (!certfilename) { + fprintf(stderr, "No signing certificate specified\n"); + return EXIT_FAILURE; + } + + /* initialise openssl */ + OpenSSL_add_all_digests(); + OpenSSL_add_all_ciphers(); + ERR_load_crypto_strings(); + + /* set up the variable signing context */ + set_varname(ctx, argv[optind]); + ctx->infilename = argv[optind+1]; + + if (!ctx->outfilename) + set_default_outfilename(ctx); + + ctx->var_attrs = parse_attrs(attr_str); + if (ctx->var_attrs == attr_invalid) + return EXIT_FAILURE; + + if (guid_str) { + if (parse_guid(guid_str, &ctx->var_guid)) { + fprintf(stderr, "Invalid GUID '%s'\n", guid_str); + return EXIT_FAILURE; + } + } else { + ctx->var_guid = default_guid; + } + + if (read_var_data(ctx)) + return EXIT_FAILURE; + + if (load_key(ctx, keyfilename)) + return EXIT_FAILURE; + + if (load_cert(ctx, certfilename)) + return EXIT_FAILURE; + + /* do the signing */ + if (add_auth_descriptor(ctx)) + return EXIT_FAILURE; + + /* write the resulting image */ + if (write_signed(ctx, include_attrs)) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +}