sbsigntools/src/sbvarsign.c
James Bottomley 73a13fb7e3 sbvarsign: use SignedData instead of PKCS7 for authenticated updates
The EFI standard is ambiguous about which one to use for variable
updates (it is definite about using PKCS7 for signed binaries).  Until
recently, the reference platform, tianocore, accepted both.  However
after patch

commit c035e37335ae43229d7e68de74a65f2c01ebc0af
Author: Zhang Lubo <lubo.zhang@intel.com>
Date:   Thu Jan 5 14:58:05 2017 +0800

    SecurityPkg: enhance secure boot Config Dxe & Time Based AuthVariable.

The acceptance of PKCS7 got broken.  This breakage seems to be
propagating to the UEFI ecosystem, so update the variable signing
tools to emit the SignedData type (which all previous EFI
implementations accepted).

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
2019-01-09 15:52:34 -08:00

571 lines
14 KiB
C

/*
* 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(&timestamp))
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, &timestamp, 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_SIGNED(p7->d.sign, 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_SIGNED(p7->d.sign, &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;
}