blob: 049ee4e47ce259e65516fd4b024ae2a573c403b5 [file] [log] [blame]
/*
* Copyright 2012 <James.Bottomley@HansenPartnership.com>
*
* see COPYING file
*
* Tool for manipulating system keys in setup mode
*/
#include <efi.h>
#include <efilib.h>
#include <console.h>
#include <simple_file.h>
#include <variables.h>
#include <guid.h>
#include <x509.h>
#include <efiauthenticated.h>
static EFI_HANDLE im;
static UINT8 SetupMode, SecureBoot, display_dbt;
#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
enum {
KEY_PK = 0,
KEY_KEK,
KEY_DB,
KEY_DBX,
KEY_DBT,
KEY_MOK,
MAX_KEYS
};
static struct {
CHAR16 *name;
CHAR16 *text;
EFI_GUID *guid;
int authenticated:1;
int hash:1;
} keyinfo[] = {
[KEY_PK] = {
.name = L"PK",
.text = L"The Platform Key (PK)",
.guid = &GV_GUID,
.authenticated = 1,
.hash = 0,
},
[KEY_KEK] = {
.name = L"KEK",
.text = L"The Key Exchange Key Database (KEK)",
.guid = &GV_GUID,
.authenticated = 1,
.hash = 0,
},
[KEY_DB] = {
.name = L"db",
.text = L"The Allowed Signatures Database (db)",
.guid = &SIG_DB,
.authenticated = 1,
.hash = 1,
},
[KEY_DBX] = {
.name = L"dbx",
.text = L"The Forbidden Signatures Database (dbx)",
.guid = &SIG_DB,
.authenticated = 1,
.hash = 1,
},
[KEY_DBT] = {
.name = L"dbt",
.text = L"The Timestamp Signatures Database (dbt)",
.guid = &SIG_DB,
.authenticated = 1,
.hash = 0,
},
[KEY_MOK] = {
.name = L"MokList",
.text = L"The Machine Owner Key List (MokList)",
.guid = &MOK_OWNER,
.authenticated = 0,
.hash = 1,
}
};
static const int keyinfo_size = ARRAY_SIZE(keyinfo);
struct {
EFI_GUID *guid;
CHAR16 *name;
} signatures[] = {
{ .guid = &X509_GUID,
.name = L"X509",
},
{ .guid = &RSA2048_GUID,
.name = L"RSA2048",
},
{ .guid = &EFI_CERT_SHA256_GUID,
.name = L"SHA256 signature",
},
{ .guid = &EFI_CERT_X509_SHA256_GUID,
.name = L"X509 SHA256 signature",
},
{ .guid = &EFI_CERT_X509_SHA384_GUID,
.name = L"X509 SHA384 signature",
},
{ .guid = &EFI_CERT_X509_SHA384_GUID,
.name = L"X509 SHA256 signature",
},
};
static const int signatures_size = ARRAY_SIZE(signatures);
static void
select_and_apply(CHAR16 **title, CHAR16 *ext, int key, UINTN options)
{
CHAR16 *file_name;
EFI_STATUS status;
EFI_FILE *file;
EFI_HANDLE h = NULL;
int use_setsecurevariable = 0;
simple_file_selector(&h, title, L"\\", ext, &file_name);
if (file_name == NULL)
return;
status = simple_file_open(h, file_name, &file, EFI_FILE_MODE_READ);
if (status != EFI_SUCCESS)
return;
UINTN size;
void *esl;
simple_file_read_all(file, &size, &esl);
simple_file_close(file);
/* PK is different: need to update with an authenticated bundle
* including a signature with the new PK */
if (StrCmp(&file_name[StrLen(file_name) - 4], L".esl") == 0) {
if (keyinfo[key].authenticated)
use_setsecurevariable = 1;
else
use_setsecurevariable = 0;
} else if (StrCmp(&file_name[StrLen(file_name) - 5], L".auth") == 0) {
use_setsecurevariable = 0;
options |= EFI_VARIABLE_RUNTIME_ACCESS
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
;
if (!keyinfo[key].authenticated) {
console_errorbox(L"Can't set MOK variables with a .auth file");
return;
}
} else {
if (keyinfo[key].authenticated)
use_setsecurevariable = 1;
else
use_setsecurevariable = 0;
void *newesl;
int newsize;
status = variable_create_esl(esl, size, &X509_GUID, NULL,
&newesl, &newsize);
if (status != EFI_SUCCESS) {
console_error(L"Failed to create proper ESL", status);
return;
}
FreePool(esl);
esl = newesl;
size = newsize;
}
if (use_setsecurevariable) {
status = SetSecureVariable(keyinfo[key].name, esl, size,
*keyinfo[key].guid, options, 0);
} else {
status = RT->SetVariable(keyinfo[key].name, keyinfo[key].guid,
EFI_VARIABLE_NON_VOLATILE
| EFI_VARIABLE_BOOTSERVICE_ACCESS
| options,
size, esl);
}
if (status != EFI_SUCCESS) {
console_error(L"Failed to update variable", status);
return;
}
}
static int
StringSplit(CHAR16 *str, int maxlen, CHAR16 c, CHAR16 **out)
{
int len = StrLen(str);
int count = 0;
if (len < maxlen) {
out[0] = str;
return 1;
}
while (len > 0) {
int i, found = 0;
for (i = 0; i < maxlen; i++) {
if (str[i] == c)
found = i;
if (str[i] == '\0') {
found = i;
break;
}
}
out[count++] = str;
str[found] = '\0';
str = str + found + 1;
len -= found + 1;
}
return count;
}
static void
delete_key(int key, void *Data, int DataSize, EFI_SIGNATURE_LIST *CertList,
EFI_SIGNATURE_DATA *Cert)
{
EFI_STATUS status;
int certs = (CertList->SignatureListSize - sizeof(EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize;
if (certs == 1) {
/* delete entire sig list + data */
DataSize -= CertList->SignatureListSize;
if (DataSize > 0)
CopyMem(CertList, (void *) CertList + CertList->SignatureListSize, DataSize - ((void *) CertList - Data));
} else {
int remain = DataSize - ((void *)Cert - Data) - CertList->SignatureSize;
/* only delete single sig */
DataSize -= CertList->SignatureSize;
CertList->SignatureListSize -= CertList->SignatureSize;
if (remain > 0)
CopyMem(Cert, (void *)Cert + CertList->SignatureSize, remain);
}
if (keyinfo[key].authenticated)
status = SetSecureVariable(keyinfo[key].name, Data,
DataSize,
*keyinfo[key].guid, 0, 0);
else
status = RT->SetVariable(keyinfo[key].name, keyinfo[key].guid,
EFI_VARIABLE_NON_VOLATILE
| EFI_VARIABLE_BOOTSERVICE_ACCESS,
DataSize, Data);
if (status != EFI_SUCCESS)
console_error(L"Failed to delete key", status);
}
static void
show_key(int key, int offset, void *Data, int DataSize)
{
EFI_SIGNATURE_LIST *CertList;
EFI_SIGNATURE_DATA *Cert = NULL;
int cert_count = 0, i, Size, option = 0;
CHAR16 *title[20], *options[4];
CHAR16 str[256], str1[256], str2[256];
title[0] = keyinfo[key].text;
certlist_for_each_certentry(CertList, Data, Size, DataSize) {
certentry_for_each_cert(Cert, CertList)
if (cert_count++ == offset)
goto finished;
}
finished:
SPrint(str, sizeof(str), L"Sig[%d] - owner: %g", offset, &Cert->SignatureOwner);
int c = 0;
title[c++] = str;
title[c] = L"Unknown";
for (i = 0; i < signatures_size; i++) {
if (CompareGuid(signatures[i].guid, &CertList->SignatureType) == 0) {
SPrint(str1, sizeof(str1), L"Type: %s", signatures[i].name);
title[c] = str1;
break;
}
}
CHAR16 buf[1024], buf1[1024], *tmpbuf[10], *tmpbuf1[10];
if (CompareGuid(&CertList->SignatureType, &EFI_CERT_SHA256_GUID) == 0) {
StrCpy(str2, L"Hash: ");
sha256_StrCat_hash(str2, Cert->SignatureData);
title[++c] = str2;
} else if (CompareGuid(&CertList->SignatureType, &X509_GUID) == 0) {
x509_to_str(Cert->SignatureData,
CertList->SignatureSize,
X509_OBJ_SUBJECT, buf, sizeof(buf));
title[++c] = L"";
title[++c] = L"Subject:";
int sp = StringSplit(buf, 70, ',', tmpbuf);
for (i = 0; i < sp; i++)
title[++c] = tmpbuf[i];
x509_to_str(Cert->SignatureData,
CertList->SignatureSize,
X509_OBJ_ISSUER, buf1, sizeof(buf1));
sp = StringSplit(buf1, 70, ',', tmpbuf1);
title[++c] = L"Issuer:";
for (i = 0; i < sp; i++)
title[++c] = tmpbuf1[i];
} else if (CompareGuid(&CertList->SignatureType, &EFI_CERT_X509_SHA256_GUID) == 0) {
EFI_CERT_X509_SHA256 *tmp = (void *)Cert->SignatureData;
StrCpy(str2, L"Hash: ");
sha256_StrCat_hash(str2, Cert->SignatureData);
title[++c] = str2;
EFI_TIME timestamp = tmp->TimeOfRevocation;
SPrint(buf, sizeof(buf),
L"Revocation Timestamp: %d-%d-%d %02d:%02d:%02d\n",
timestamp.Year, timestamp.Month, timestamp.Day,
timestamp.Hour, timestamp.Minute, timestamp.Second);
title[++c] = buf;
}
title[++c] = NULL;
int o = 0;
int option_delete = NOSEL, option_delete_w_auth = NOSEL,
option_save = NOSEL;
if (variable_is_setupmode() || key == KEY_MOK) {
option_delete = o;
options[o++] = L"Delete";
}
option_save = o;
options[o++] = L"Save to File";
if (key == KEY_PK) {
option_delete_w_auth = o;
options[o++] = L"Delete with .auth File";
}
options[o++] = NULL;
option = console_select(title, options, option);
if (option == -1)
return;
if (option == option_delete) {
delete_key(key, Data, DataSize, CertList, Cert);
} else if (option == option_save) {
CHAR16 *filename;
EFI_FILE *file;
EFI_STATUS status;
EFI_HANDLE vol;
CHAR16 *volname;
simple_volume_selector((CHAR16 *[]) {
L"Save Key",
L"",
L"Select a disk Volume to save the key file to",
L"The Key file will be saved in the top level directory",
L"",
L"Note: For USB volumes, some UEFI implementations aren't",
L"very good at hotplug, so you may have to boot with the USB",
L"Key already plugged in to see the volume",
NULL
}, &volname, &vol);
/* no selection or ESC pressed */
if (!volname)
return;
FreePool(volname);
filename = AllocatePool(1024);
SPrint(filename, 0, L"%s-%d.esl", keyinfo[key].name, offset);
status = simple_file_open(vol, filename, &file, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE);
if (status == EFI_SUCCESS) {
status = simple_file_write_all(file, CertList->SignatureListSize, CertList);
simple_file_close(file);
}
if (status != EFI_SUCCESS) {
CHAR16 str[80];
SPrint(str, sizeof(str), L"Failed to write %s", filename);
console_error(str, status);
} else {
CHAR16 str1[80], str2[80], str3[80];
SPrint(str1, sizeof(str1), L"Key %s[%d]", keyinfo[key].name, offset);
SPrint(str2, sizeof(str2), L"With GUID: %g", &Cert->SignatureOwner);
SPrint(str3, sizeof(str3), L"saved to %s", filename);
console_alertbox((CHAR16 *[]) {
L"Successfully Saved",
L"",
str1,
str2,
str3,
NULL
});
}
FreePool(filename);
} else if (option == option_delete_w_auth) {
title[0] = L"Select authority bundle to remove PK";
title[1] = NULL;
select_and_apply(title, L".auth", key, 0);
}
}
static void
add_new_key(int key, UINTN options)
{
CHAR16 *title[3];
/* PK update must be signed: so require .auth file */
CHAR16 *ext = (key != KEY_PK && variable_is_setupmode())
? L".esl|.auth|.cer" : L".auth";
title[0] = L"Select File containing additional key for";
title[1] = keyinfo[key].text;
title[2] = NULL;
select_and_apply(title, ext, key, options);
}
static void
enroll_hash(int key)
{
EFI_STATUS efi_status;
CHAR16 *file_name = NULL, *title[6], buf0[256], buf1[256], buf2[256];
UINT8 hash[SHA256_DIGEST_SIZE];
int i;
EFI_HANDLE h = NULL;
simple_file_selector(&h, (CHAR16 *[]){
L"Select Binary",
L"",
L"The Selected Binary will have its hash Enrolled",
L"This means it will Subsequently Boot with no prompting",
L"Remember to make sure it is a genuine binary before Enrolling its hash",
NULL
}, L"\\", NULL, &file_name);
if (!file_name)
/* user pressed ESC */
return;
efi_status = sha256_get_pecoff_digest(h, file_name, hash);
if (efi_status != EFI_SUCCESS) {
console_error(L"Hash failed (is efi binary valid?)",
efi_status);
return;
}
StrCpy(buf0, L"Enroll hash into ");
StrCat(buf0, keyinfo[key].text);
title[0] = buf0;
title[1] = L"";
StrCpy(buf1, L"File: ");
StrCat(buf1, file_name);
title[2] = buf1;
StrCpy(buf2, L"Hash: ");
sha256_StrCat_hash(buf2, hash);
title[3] = buf2;
title[4] = NULL;
i = console_yes_no(title);
if (i == 0)
return;
efi_status = variable_enroll_hash(keyinfo[key].name,
*keyinfo[key].guid, hash);
if (efi_status != EFI_SUCCESS && efi_status != EFI_ALREADY_STARTED) {
console_error(L"Failed to add signature to db", efi_status);
return;
}
}
static void
save_key_internal(int key, EFI_HANDLE vol, CHAR16 *error)
{
EFI_STATUS status;
EFI_FILE *file;
UINT8 *data;
UINTN len;
CHAR16 file_name[512];
StrCpy(error, keyinfo[key].name);
status = get_variable(keyinfo[key].name, &data, &len,
*keyinfo[key].guid);
if (status != EFI_SUCCESS) {
if (status == EFI_NOT_FOUND)
StrCat(error, L": Variable has no entries");
else
SPrint(error, 1024, L"%s: Failed to get variable (Error: %d)",
error, status);
return;
}
StrCpy(file_name, L"\\");
StrCat(file_name, keyinfo[key].name);
StrCat(file_name, L".esl");
status = simple_file_open(vol, file_name, &file,
EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
| EFI_FILE_MODE_CREATE);
if (status != EFI_SUCCESS) {
SPrint(error, 1024, L"%s: Failed to open file %s (Error: %d)",
error, file_name, status);
return;
}
status = simple_file_write_all(file, len, data);
simple_file_close(file);
if (status != EFI_SUCCESS) {
SPrint(error, 1024, L"%s: Failed to write to %s (Error: %d)",
error, file_name, status);
return;
}
StrCat(error, L": Successfully written to ");
StrCat(error, file_name);
}
static void
save_key(int key)
{
EFI_HANDLE vol;
CHAR16 *volname;
simple_volume_selector((CHAR16 *[]) {
L"Save Key",
L"",
L"Select a disk Volume to save the key file to",
L"The key file will be saved in the top level directory",
L"",
L"Note: For USB volumes, some UEFI implementations aren't",
L"very good at hotplug, so you may have to boot with the USB",
L"USB device already plugged in to see the volume",
NULL
}, &volname, &vol);
/* no selection or ESC pressed */
if (!volname)
return;
FreePool(volname);
CHAR16 buf[1024], *title[2];
save_key_internal(key, vol, buf);
title[0] = buf;
title[1] = NULL;
console_alertbox(title);
}
static void
manipulate_key(int key)
{
CHAR16 *title[5];
EFI_STATUS efi_status;
int setup_mode = variable_is_setupmode(), i;
title[0] = L"Manipulating Contents of";
title[1] = keyinfo[key].text;
title[2] = NULL;
UINT8 *Data;
UINTN DataSize = 0, Size;
efi_status = RT->GetVariable(keyinfo[key].name, keyinfo[key].guid, NULL, &DataSize, NULL);
if (efi_status != EFI_BUFFER_TOO_SMALL && efi_status != EFI_NOT_FOUND) {
console_error(L"Failed to get DataSize", efi_status);
return;
}
Data = AllocatePool(DataSize);
if (!Data) {
CHAR16 str[80];
SPrint(str, sizeof(str), L"Failed to allocate %d", DataSize);
console_errorbox(str);
return;
}
efi_status = RT->GetVariable(keyinfo[key].name, keyinfo[key].guid, NULL, &DataSize, Data);
if (efi_status == EFI_NOT_FOUND) {
int t = 2;
title[t++] = L"Variable is Empty";
if (key == KEY_PK)
title[t++] = L"WARNING: Setting PK will take the platform out of Setup Mode";
title[t++] = NULL;
} else if (efi_status != EFI_SUCCESS) {
console_error(L"Failed to get variable", efi_status);
return;
}
EFI_SIGNATURE_LIST *CertList;
int cert_count = 0, add = NOSEL, replace = NOSEL, hash = NOSEL,
save = NOSEL;
certlist_for_each_certentry(CertList, Data, Size, DataSize) {
cert_count += (CertList->SignatureListSize - sizeof(EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize;
}
CHAR16 **guids = (CHAR16 **)AllocatePool((cert_count + 5)*sizeof(void *));
cert_count = 0;
int g;
certlist_for_each_certentry(CertList, Data, Size, DataSize) {
EFI_SIGNATURE_DATA *Cert;
certentry_for_each_cert(Cert, CertList) {
guids[cert_count] = AllocatePool(64*sizeof(CHAR16));
SPrint(guids[cert_count++], 64*sizeof(CHAR16), L"%g", &Cert->SignatureOwner);
}
}
g = cert_count;
if (key != 0) {
add = g;
guids[g++] = L"Add New Key";
}
replace = g;
guids[g++] = L"Replace Key(s)";
if (keyinfo[key].hash && (!keyinfo[key].authenticated || setup_mode)) {
hash = g;
guids[g++] = L"Enroll hash of binary";
}
if (cert_count != 0) {
save = g;
guids[g++] = L"Save key";
}
guids[g] = NULL;
int select = console_select(title, guids, 0);
for (i = 0; i < cert_count; i++)
FreePool(guids[i]);
FreePool(guids);
if (select == replace)
add_new_key(key, 0);
else if (select == add)
add_new_key(key, EFI_VARIABLE_APPEND_WRITE);
else if (select == hash)
enroll_hash(key);
else if (select == save)
save_key(key);
else if (select >= 0)
show_key(key, select, Data, DataSize);
FreePool(Data);
}
static void
select_key(void)
{
int i, j;
int keymap[keyinfo_size + 1];
CHAR16 *keys[keyinfo_size + 1];
for (i = 0, j = 0; i < keyinfo_size; i++) {
if (i == KEY_DBT && !display_dbt)
continue;
keys[j] = keyinfo[i].text;
keymap[j++] = i;
}
keys[j] = NULL;
i = 0;
for (;;) {
i = console_select( (CHAR16 *[]){ L"Select Key to Manipulate", NULL }, keys, i);
if (i == -1)
break;
manipulate_key(keymap[i]);
}
}
static void
save_keys(void)
{
EFI_HANDLE vol;
CHAR16 *volname;
simple_volume_selector((CHAR16 *[]) {
L"Save Keys",
L"",
L"Select a disk Volume to save all the key files to",
L"Key files will be saved in the top level directory",
L"",
L"Note: For USB volumes, some UEFI implementations aren't",
L"very good at hotplug, so you may have to boot with the USB",
L"USB device already plugged in to see the volume",
NULL
}, &volname, &vol);
/* no selection or ESC pressed */
if (!volname)
return;
FreePool(volname);
CHAR16 *title[10], buf[8000];
int i, t_c = 0, b_c = 0;
title[t_c++] = L"Results of Saving Keys";
title[t_c++] = L"";
for (i = 0; i < MAX_KEYS; i++) {
if (i == KEY_DBT && !display_dbt)
continue;
save_key_internal(i, vol, &buf[b_c]);
title[t_c++] = &buf[b_c];
b_c += StrLen(&buf[b_c]) + 1;
}
title[t_c] = NULL;
console_alertbox(title);
}
static void
execute_binary()
{
CHAR16 *bin_name;
EFI_HANDLE h = NULL;
EFI_HANDLE ih;
EFI_DEVICE_PATH *devpath;
EFI_STATUS status;
simple_file_selector(&h, (CHAR16 *[]) {
L"Select Binary to Execute",
L"",
NULL
}, L"\\",
NULL, &bin_name);
if (!bin_name) {
/* user pressed ESC */
return;
}
/* the execute() call is designed to construct handles from
* local resources on the image. We have a handle and a full
* path name, so we follow proper process here */
devpath = FileDevicePath(h, bin_name);
status = BS->LoadImage(FALSE, im, devpath, NULL, 0, &ih);
if (status != EFI_SUCCESS) {
console_error(L"Image failed to load", status);
return;
}
status = BS->StartImage(ih, NULL, NULL);
BS->UnloadImage(ih);
if (status != EFI_SUCCESS)
console_error(L"Execution returned error", status);
}
EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
EFI_STATUS efi_status;
UINTN DataSize = sizeof(SetupMode);
int option = 0;
im = image;
InitializeLib(image, systab);
if (GetOSIndications() & EFI_OS_INDICATIONS_TIMESTAMP_REVOCATION)
display_dbt = 1;
efi_status = RT->GetVariable(L"SetupMode", &GV_GUID, NULL, &DataSize, &SetupMode);
if (efi_status != EFI_SUCCESS) {
Print(L"No SetupMode variable ... is platform secure boot enabled?\n"); return EFI_SUCCESS;
}
for (;;) {
CHAR16 line2[80], line3[80], **title;
SetupMode = variable_is_setupmode();
SecureBoot = variable_is_secureboot();
line2[0] = line3[0] = L'\0';
StrCat(line2, L"Platform is in ");
StrCat(line2, SetupMode ? L"Setup Mode" : L"User Mode");
StrCat(line3, L"Secure Boot is ");
StrCat(line3, SecureBoot ? L"on" : L"off");
title = (CHAR16 *[]){L"KeyTool main menu", L"", line2, line3, NULL };
option = console_select(title, (CHAR16 *[]){
L"Save Keys",
L"Edit Keys",
L"Execute Binary",
L"Exit",
NULL },
option);
switch (option) {
case 0:
save_keys();
break;
case 1:
select_key();
break;
case 2:
execute_binary();
break;
case 3:
/* exit from programme */
return EFI_SUCCESS;
default:
break;
}
}
return EFI_SUCCESS;
}