| /* |
| * Copyright (C) 2012 Jeremy Kerr <jeremy.kerr@canonical.com> |
| * |
| * 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 <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <getopt.h> |
| |
| #include <uuid/uuid.h> |
| |
| #include <openssl/bio.h> |
| #include <openssl/conf.h> |
| #include <openssl/evp.h> |
| #include <openssl/err.h> |
| #include <openssl/pem.h> |
| #include <openssl/pkcs7.h> |
| |
| #include <ccan/array_size/array_size.h> |
| #include <ccan/read_write_all/read_write_all.h> |
| #include <ccan/talloc/talloc.h> |
| |
| #include "efivars.h" |
| #include "fileio.h" |
| |
| static const char *toolname = "sbvarsign"; |
| |
| struct varsign_context { |
| const char *infilename; |
| const char *outfilename; |
| |
| uint8_t *data; |
| size_t 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; |
| }; |
| |
| #define EFI_VAR_ATTR(n) { #n, EFI_VARIABLE_ ## n } |
| static struct attr attrs[] = { |
| EFI_VAR_ATTR(NON_VOLATILE), |
| EFI_VAR_ATTR(BOOTSERVICE_ACCESS), |
| EFI_VAR_ATTR(RUNTIME_ACCESS), |
| EFI_VAR_ATTR(TIME_BASED_AUTHENTICATED_WRITE_ACCESS), |
| EFI_VAR_ATTR(APPEND_WRITE), |
| }; |
| |
| static uint32_t default_attrs = EFI_VARIABLE_NON_VOLATILE | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | |
| EFI_VARIABLE_RUNTIME_ACCESS | |
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | |
| EFI_VARIABLE_APPEND_WRITE; |
| |
| 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[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); |
| |
| for (i = 0; i < len; i++) |
| wstr[i] = str[i]; |
| |
| ctx->var_name = wstr; |
| ctx->var_name_bytes = len * 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 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 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(auth->AuthInfo); |
| 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 void set_default_guid(struct varsign_context *ctx, const char *varname) |
| { |
| EFI_GUID secdb_guid = EFI_IMAGE_SECURITY_DATABASE_GUID; |
| EFI_GUID global_guid = EFI_GLOBAL_VARIABLE; |
| |
| if (!strcmp(varname, "db") || !strcmp(varname, "dbx")) |
| ctx->var_guid = secdb_guid; |
| else |
| ctx->var_guid = global_guid; |
| } |
| |
| 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' }, |
| { "engine", required_argument, NULL, 'e'}, |
| { NULL, 0, NULL, 0 }, |
| }; |
| |
| void usage(void) |
| { |
| unsigned int i; |
| |
| printf("Usage: %s [options] --key <keyfile> --cert <certfile> " |
| "<var-name> <var-data-file>\n" |
| "Sign a blob of data for use in SetVariable().\n\n" |
| "Options:\n" |
| "\t--engine <eng> use the specified engine to load the key\n" |
| "\t--key <keyfile> signing key (PEM-encoded RSA " |
| "private key)\n" |
| "\t--cert <certfile> certificate (x509 certificate)\n" |
| "\t--include-attrs include attrs at beginning of output file\n" |
| "\t--guid <GUID> EFI GUID for the variable. If omitted,\n" |
| "\t EFI_IMAGE_SECURITY_DATABASE or\n" |
| "\t EFI_GLOBAL_VARIABLE (depending on\n" |
| "\t <var-name>) will be used.\n" |
| "\t--attr <attrs> 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 default is all attributes,\n" |
| "\t TIME_BASED_AUTH... is always included.\n" |
| "\t--output <file> write signed data to <file>\n" |
| "\t (default <var-data-file>.signed)\n"); |
| } |
| |
| static void version(void) |
| { |
| printf("%s %s\n", toolname, VERSION); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const char *guid_str, *attr_str, *varname, *engine; |
| const char *keyfilename, *certfilename; |
| struct varsign_context *ctx; |
| bool include_attrs; |
| int c; |
| |
| ctx = talloc_zero(NULL, struct varsign_context); |
| |
| keyfilename = NULL; |
| certfilename = NULL; |
| engine = NULL; |
| guid_str = NULL; |
| attr_str= NULL; |
| include_attrs = false; |
| |
| for (;;) { |
| int idx; |
| c = getopt_long(argc, argv, "o:g:a:k:c:ivVhe:", 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; |
| case 'e': |
| engine = optarg; |
| break; |
| } |
| } |
| |
| 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(); |
| OPENSSL_config(NULL); |
| /* here we may get highly unlikely failures or we'll get a |
| * complaint about FIPS signatures (usually becuase the FIPS |
| * module isn't present). In either case ignore the errors |
| * (malloc will cause other failures out lower down */ |
| ERR_clear_error(); |
| |
| /* set up the variable signing context */ |
| varname = argv[optind]; |
| set_varname(ctx, varname); |
| ctx->infilename = argv[optind+1]; |
| |
| if (!ctx->outfilename) |
| set_default_outfilename(ctx); |
| |
| if (attr_str) { |
| ctx->var_attrs = parse_attrs(attr_str); |
| if (ctx->var_attrs == attr_invalid) |
| return EXIT_FAILURE; |
| } else { |
| ctx->var_attrs = default_attrs; |
| } |
| |
| if (guid_str) { |
| if (parse_guid(guid_str, &ctx->var_guid)) { |
| fprintf(stderr, "Invalid GUID '%s'\n", guid_str); |
| return EXIT_FAILURE; |
| } |
| } else { |
| set_default_guid(ctx, varname); |
| } |
| |
| if (fileio_read_file(ctx, ctx->infilename, &ctx->data, &ctx->data_len)) |
| return EXIT_FAILURE; |
| |
| if (engine) |
| ctx->key = fileio_read_engine_key(engine, keyfilename); |
| else |
| ctx->key = fileio_read_pkey(keyfilename); |
| if (!ctx->key) |
| return EXIT_FAILURE; |
| |
| ctx->cert = fileio_read_cert(certfilename); |
| if (!ctx->cert) |
| 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; |
| } |