blob: 4247105958f9e4b46c050c377f9b8460c063f4fb [file] [log] [blame]
/*
* Copyright 2013 <James.Bottomley@HansenPartnership.com>
*
* see COPYING file
*/
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#define __STDC_VERSION__ 199901L
#include <efi.h>
#include <kernel_efivars.h>
#include <openssl_sign.h>
#include <guid.h>
#include <sha256.h>
#include <version.h>
#include "efiauthenticated.h"
#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
static void
usage(const char *progname)
{
printf("Usage: %s: [-a] [-e] [-d <list>[-<entry>]] [-k <key>] [-g <guid>] [-b <file>|-f <file>|-c file] <var>\n", progname);
}
static void
help(const char *progname)
{
usage(progname);
printf("Manipulate the UEFI key database via the efivarfs filesystem\n\n"
"Options:\n"
"\t-a\tappend a value to the variable instead of replacing it\n"
"\t-e\tuse EFI Signature List instead of signed update (only works in Setup Mode\n"
"\t-b <binfile>\tAdd hash of <binfile> to the signature list\n"
"\t-f <file>\tAdd or Replace the key file (.esl or .auth) to the <var>\n"
"\t-c <file>\tAdd or Replace the x509 certificate to the <var> (with <guid> if provided)\n"
"\t-g <guid>\tOptional <guid> for the X509 Certificate\n"
"\t-k <key>\tSecret key file for authorising User Mode updates\n"
"\t-d <list>[-<entry>]\tDelete the signature list <list> (or just a single <entry> within the list)\n"
"\t--engine <eng>\tUse engine <eng> for private key\n"
);
}
int
main(int argc, char *argv[])
{
char *variables[] = { "PK", "KEK", "db", "dbx" };
char *signedby[] = { "PK", "PK", "KEK", "KEK" };
char *engine = NULL;
EFI_GUID *owners[] = { &GV_GUID, &GV_GUID, &SIG_DB, &SIG_DB };
EFI_GUID *owner, guid = MOK_OWNER;
int i, esl_mode = 0, fd, ret, delsig = -1, delentry = -1;
struct stat st;
uint32_t attributes = EFI_VARIABLE_NON_VOLATILE
| EFI_VARIABLE_RUNTIME_ACCESS
| EFI_VARIABLE_BOOTSERVICE_ACCESS
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
char *hash_mode = NULL, *file = NULL, *var, *progname = argv[0], *buf,
*name, *crt_file = NULL, *key_file = NULL;
while (argc > 1 && argv[1][0] == '-') {
if (strcmp("--version", argv[1]) == 0) {
version(progname);
exit(0);
} else if (strcmp("--help", argv[1]) == 0) {
help(progname);
exit(0);
} else if(strcmp(argv[1], "-a") == 0) {
attributes |= EFI_VARIABLE_APPEND_WRITE;
argv += 1;
argc -= 1;
} if (strcmp(argv[1], "-e") == 0) {
esl_mode = 1;
argv += 1;
argc -= 1;
} else if (strcmp(argv[1], "-b") == 0) {
hash_mode = argv[2];
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "-f") == 0) {
file = argv[2];
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "-g") == 0) {
if (str_to_guid(argv[2], &guid)) {
fprintf(stderr, "Invalid GUID %s\n", argv[2]);
exit(1);
}
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "-c") == 0) {
crt_file = argv[2];
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "-k") == 0) {
key_file = argv[2];
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "-d") == 0) {
sscanf(argv[2], "%d-%d", &delsig, &delentry);
argv += 2;
argc -= 2;
} else if (strcmp(argv[1], "--engine") == 0) {
engine = argv[2];
argv += 2;
argc -= 2;
} else {
/* unrecognised option */
break;
}
}
if (argc != 2) {
usage(progname);
exit(1);
}
var = argv[1];
for(i = 0; i < ARRAY_SIZE(variables); i++) {
if (strcmp(var, variables[i]) == 0) {
owner = owners[i];
break;
}
}
if (i == ARRAY_SIZE(variables)) {
fprintf(stderr, "Invalid Variable %s\nVariable must be one of: ", var);
for (i = 0; i < ARRAY_SIZE(variables); i++)
fprintf(stderr, "%s ", variables[i]);
fprintf(stderr, "\n");
exit(1);
}
if (delsig == -1 && (!!file + !!hash_mode + !!crt_file != 1)) {
fprintf(stderr, "must specify exactly one of -f, -b or -c\n");
exit(1);
}
kernel_variable_init();
ERR_load_crypto_strings();
OpenSSL_add_all_digests();
OpenSSL_add_all_ciphers();
name = file ? file : hash_mode;
if (delsig != -1) {
uint32_t len;
int status = get_variable_alloc(variables[i], owners[i], NULL,
&len, (uint8_t **)&buf);
if (status == ENOENT) {
fprintf(stderr, "Variable %s has no entries\n", variables[i]);
exit(1);
}
EFI_SIGNATURE_LIST *CertList = (EFI_SIGNATURE_LIST *)buf;
EFI_SIGNATURE_DATA *Cert;
int size, DataSize = len, count = 0;
certlist_for_each_certentry(CertList, buf, size, DataSize) {
int Index = 0;
if (count++ != delsig)
continue;
if (delentry == -1)
goto found;
certentry_for_each_cert(Cert, CertList) {
if (Index++ == delentry)
goto found;
}
}
if (delentry == -1)
fprintf(stderr, "signature %d does not exist in %s\n", delsig, variables[i]);
else
fprintf(stderr, "signature %d-%d does not exist in %s\n", delsig, delentry, variables[i]);
exit(1);
found:
;
int certs = (CertList->SignatureListSize - sizeof(EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize;
if (certs == 1 || delentry == -1) {
/* delete entire sig list + data */
DataSize -= CertList->SignatureListSize;
if (DataSize > 0)
memcpy(CertList, (void *) CertList + CertList->SignatureListSize, DataSize - ((char *) CertList - buf));
} else {
int remain = DataSize - ((char *)Cert - buf) - CertList->SignatureSize;
/* only delete single sig */
DataSize -= CertList->SignatureSize;
CertList->SignatureListSize -= CertList->SignatureSize;
if (remain > 0)
memcpy(Cert, (void *)Cert + CertList->SignatureSize, remain);
}
st.st_size = DataSize; /* reduce length of buf */
esl_mode = 1;
} else if (name) {
fd = open(name, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to read file %s: ", name);
perror("");
exit(1);
}
if (fstat(fd, &st) < 0) {
perror("stat failed");
exit(1);
}
buf = malloc(st.st_size);
read(fd, buf, st.st_size);
close(fd);
} else {
X509 *X = NULL;
BIO *bio;
char *crt_file_ext = &crt_file[strlen(crt_file) - 4];
esl_mode = 1;
bio = BIO_new_file(crt_file, "r");
if (!bio) {
fprintf(stderr, "Failed to load certificate from %s\n", crt_file);
ERR_print_errors_fp(stderr);
exit(1);
}
if (strcasecmp(crt_file_ext, ".der") == 0
|| strcasecmp(crt_file_ext, ".cer") == 0)
/* DER format */
X = d2i_X509_bio(bio, NULL);
else
/* else assume PEM */
X = PEM_read_bio_X509(bio, NULL, NULL, NULL);
if (!X) {
fprintf(stderr, "Failed to load certificate from %s\n", crt_file);
ERR_print_errors_fp(stderr);
exit(1);
}
BIO_free_all(bio);
int cert_len = i2d_X509(X, NULL);
cert_len += sizeof(EFI_SIGNATURE_LIST) + OFFSET_OF(EFI_SIGNATURE_DATA, SignatureData);
EFI_SIGNATURE_LIST *esl = malloc(cert_len);
unsigned char *tmp = (unsigned char *)esl + sizeof(EFI_SIGNATURE_LIST) + OFFSET_OF(EFI_SIGNATURE_DATA, SignatureData);
i2d_X509(X, &tmp);
esl->SignatureListSize = cert_len;
esl->SignatureSize = (cert_len - sizeof(EFI_SIGNATURE_LIST));
esl->SignatureHeaderSize = 0;
esl->SignatureType = EFI_CERT_X509_GUID;
EFI_SIGNATURE_DATA *sig_data = (void *)esl + sizeof(EFI_SIGNATURE_LIST);
sig_data->SignatureOwner = guid;
buf = (char *)esl;
st.st_size = cert_len;
}
if (hash_mode) {
uint8_t hash[SHA256_DIGEST_SIZE];
EFI_STATUS status;
int len;
esl_mode = 1;
attributes |= EFI_VARIABLE_APPEND_WRITE;
status = sha256_get_pecoff_digest_mem(buf, st.st_size, hash);
free(buf);
if (status != EFI_SUCCESS) {
fprintf(stderr, "Failed to get hash of %s\n", name);
exit(1);
}
buf = (char *)hash_to_esl(&guid, &len, hash);
st.st_size = len;
}
if (esl_mode && (!variable_is_setupmode() || strcmp(variables[i], "PK") == 0)) {
if (!key_file) {
fprintf(stderr, "Can't update variable%s without a key\n", variable_is_setupmode() ? "" : " in User Mode");
exit(1);
}
EVP_PKEY *pkey = read_private_key(engine, key_file);
if (!pkey) {
fprintf(stderr, "error reading private key %s\n", key_file);
exit(1);
}
uint8_t *esl;
uint32_t esl_len;
int ret = get_variable_alloc(signedby[i], &GV_GUID, NULL,
&esl_len, &esl);
if (ret != 0) {
fprintf(stderr, "Failed to get %s: ", signedby[i]);
perror("");
exit(1);
}
EFI_SIGNATURE_LIST *CertList = (EFI_SIGNATURE_LIST *)esl;
int DataSize = esl_len, size;
X509 *X = NULL;
certlist_for_each_certentry(CertList, esl, size, DataSize) {
EFI_SIGNATURE_DATA *Cert;
if (compare_guid(&CertList->SignatureType, &X509_GUID) != 0)
continue;
certentry_for_each_cert(Cert, CertList) {
const unsigned char *psig = (unsigned char *)Cert->SignatureData;
X = d2i_X509(NULL, &psig, CertList->SignatureSize);
if (X509_check_private_key(X, pkey))
goto out;
X = NULL;
}
}
out:
if (!X) {
fprintf(stderr, "No public key matching %s in %s\n", key_file, signedby[i]);
exit (1);
}
EFI_TIME timestamp;
time_t t;
unsigned char *tmp;
int sigsize;
struct tm *tm;
memset(&timestamp, 0, sizeof(timestamp));
time(&t);
tm = gmtime(&t);
/* FIXME: currently timestamp is one year into future because of
* the way we set up the secure environment */
timestamp.Year = tm->tm_year + 1900 + 1;
timestamp.Month = tm->tm_mon + 1;
timestamp.Day = tm->tm_mday;
timestamp.Hour = tm->tm_hour;
timestamp.Minute = tm->tm_min;
timestamp.Second = tm->tm_sec;
/* signature is over variable name (no null and uc16
* chars), the vendor GUID, the attributes, the
* timestamp and the contents */
int signbuflen = strlen(var)*2 + sizeof(EFI_GUID) + sizeof(attributes) + sizeof(timestamp) + st.st_size;
char *signbuf = malloc(signbuflen);
char *ptr = signbuf;
int j;
for (j = 0; j < strlen(var); j++) {
*(ptr++) = var[j];
*(ptr++) = 0;
}
memcpy(ptr, owners[i], sizeof(*owners[i]));
ptr += sizeof(*owners[i]);
memcpy(ptr, &attributes, sizeof(attributes));
ptr += sizeof(attributes);
memcpy(ptr, &timestamp, sizeof(timestamp));
ptr += sizeof(timestamp);
memcpy(ptr, buf, st.st_size);
sign_efi_var_ssl(signbuf, signbuflen, pkey, X, &tmp, &sigsize);
EFI_VARIABLE_AUTHENTICATION_2 *var_auth = malloc(sizeof(EFI_VARIABLE_AUTHENTICATION_2) + sigsize);
var_auth->TimeStamp = timestamp;
var_auth->AuthInfo.CertType = EFI_CERT_TYPE_PKCS7_GUID;
var_auth->AuthInfo.Hdr.dwLength = sigsize + OFFSET_OF(WIN_CERTIFICATE_UEFI_GUID, CertData);
var_auth->AuthInfo.Hdr.wRevision = 0x0200;
var_auth->AuthInfo.Hdr.wCertificateType = WIN_CERT_TYPE_EFI_GUID;
memcpy(var_auth->AuthInfo.CertData, tmp, sigsize);
ERR_print_errors_fp(stderr);
/* new update now consists of two parts: the
* authentication header with the signature and the
* payload (the original esl) */
int siglen = OFFSET_OF(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsize;
char *newbuf = malloc(siglen + st.st_size);
memcpy(newbuf, var_auth, siglen);
memcpy(newbuf + siglen, buf, st.st_size);
free(buf);
free(esl);
free(var_auth);
buf = newbuf;
st.st_size = siglen + st.st_size;
esl_mode = 0;
}
if (esl_mode) {
ret = set_variable_esl(var, owner, attributes, st.st_size, buf);
} else {
ret = set_variable(var, owner, attributes, st.st_size, buf);
}
if (ret == EACCES) {
fprintf(stderr, "Cannot write to %s, wrong filesystem permissions\n", var);
exit(1);
} else if (ret != 0) {
fprintf(stderr, "Failed to update %s: ", var);
perror("");
exit(1);
}
return 0;
}