diff options
| -rw-r--r-- | Makefile | 48 | ||||
| -rw-r--r-- | MokManager.c | 1692 | ||||
| -rw-r--r-- | MokVars.txt | 51 | ||||
| -rw-r--r-- | dbx.S | 32 | ||||
| -rwxr-xr-x | make-certs | 554 | ||||
| -rw-r--r-- | netboot.c | 370 | ||||
| -rw-r--r-- | netboot.h | 9 | ||||
| -rw-r--r-- | shim.c | 723 | ||||
| -rw-r--r-- | shim.h | 22 | ||||
| -rw-r--r-- | signature.h | 5 |
10 files changed, 3354 insertions, 152 deletions
@@ -26,20 +26,48 @@ endif LDFLAGS = -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(EFI_PATH) -L$(LIB_PATH) -LCryptlib -LCryptlib/OpenSSL $(EFI_CRT_OBJS) -VERSION = 0.1 +VERSION = 0.2 -TARGET = shim.efi -OBJS = shim.o cert.o -SOURCES = shim.c shim.h signature.h PeImage.h +TARGET = shim.efi MokManager.efi.signed +OBJS = shim.o netboot.o cert.o dbx.o +KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key +SOURCES = shim.c shim.h netboot.c signature.h PeImage.h +MOK_OBJS = MokManager.o +MOK_SOURCES = MokManager.c shim.h all: $(TARGET) -shim.o: $(SOURCES) +shim.crt: + ./make-certs shim shim@xn--u4h.net all codesign 1.3.6.1.4.1.311.10.3.1 </dev/null + +shim.cer: shim.crt + openssl x509 -outform der -in $< -out $@ + +shim_cert.h: shim.cer + echo "static UINT8 shim_cert[] = {" > $@ + hexdump -v -e '1/1 "0x%02x, "' $< >> $@ + echo "};" >> $@ + +certdb/secmod.db: shim.crt + -mkdir certdb + certutil -A -n 'my CA' -d certdb/ -t CT,CT,CT -i ca.crt + pk12util -d certdb/ -i shim.p12 -W "" -K "" + certutil -d certdb/ -A -i shim.crt -n shim -t u + +shim.o: $(SOURCES) shim_cert.h cert.o : cert.S $(CC) $(CFLAGS) -c -o $@ $< -shim.so: $(OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a cert.o +dbx.o : dbx.S + $(CC) $(CFLAGS) -c -o $@ $< + +shim.so: $(OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a + $(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS) + +MokManager.o: $(SOURCES) + +MokManager.so: $(MOK_OBJS) Cryptlib/libcryptlib.a Cryptlib/OpenSSL/libopenssl.a $(LD) -o $@ $(LDFLAGS) $^ $(EFI_LIBS) Cryptlib/libcryptlib.a: @@ -58,12 +86,16 @@ Cryptlib/OpenSSL/libopenssl.a: -j .rela -j .reloc -j .eh_frame \ -j .debug_info -j .debug_abbrev -j .debug_aranges \ -j .debug_line -j .debug_str -j .debug_ranges \ - --target=efi-app-$(ARCH) $^ shim.efi.debug + --target=efi-app-$(ARCH) $^ $@.debug + +%.efi.signed: %.efi certdb/secmod.db + pesign -n certdb -i $< -c "shim" -s -o $@ -f clean: $(MAKE) -C Cryptlib clean $(MAKE) -C Cryptlib/OpenSSL clean - rm -f $(TARGET) $(OBJS) + rm -rf $(TARGET) $(OBJS) $(MOK_OBJS) $(KEYS) certdb + rm -f *.debug *.so GITTAG = $(VERSION) diff --git a/MokManager.c b/MokManager.c new file mode 100644 index 00000000..5802d274 --- /dev/null +++ b/MokManager.c @@ -0,0 +1,1692 @@ +#include <efi.h> +#include <efilib.h> +#include <Library/BaseCryptLib.h> +#include <openssl/x509.h> +#include "shim.h" +#include "signature.h" +#include "PeImage.h" + +#define PASSWORD_MAX 16 +#define PASSWORD_MIN 8 +#define SB_PASSWORD_LEN 8 + +#ifndef SHIM_VENDOR +#define SHIM_VENDOR L"Shim" +#endif + +#define EFI_VARIABLE_APPEND_WRITE 0x00000040 + +#define CERT_STRING L"Select an X509 certificate to enroll:\n\n" +#define HASH_STRING L"Select a file to trust:\n\n" + +struct menu_item { + CHAR16 *text; + INTN (* callback)(void *data, void *data2, void *data3); + void *data; + void *data2; + void *data3; + UINTN colour; +}; + +typedef struct { + UINT32 MokSize; + UINT8 *Mok; +} __attribute__ ((packed)) MokListNode; + +typedef struct { + UINT32 MokSBState; + UINT32 PWLen; + CHAR16 Password[PASSWORD_MAX]; +} __attribute__ ((packed)) MokSBvar; + +static EFI_INPUT_KEY get_keystroke (void) +{ + EFI_INPUT_KEY key; + UINTN EventIndex; + + uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, + &EventIndex); + uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key); + + return key; +} + +static EFI_STATUS get_sha1sum (void *Data, int DataSize, UINT8 *hash) +{ + EFI_STATUS status; + unsigned int ctxsize; + void *ctx = NULL; + + ctxsize = Sha1GetContextSize(); + ctx = AllocatePool(ctxsize); + + if (!ctx) { + Print(L"Unable to allocate memory for hash context\n"); + return EFI_OUT_OF_RESOURCES; + } + + if (!Sha1Init(ctx)) { + Print(L"Unable to initialise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + if (!(Sha1Update(ctx, Data, DataSize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + if (!(Sha1Final(ctx, hash))) { + Print(L"Unable to finalise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + status = EFI_SUCCESS; +done: + return status; +} + +static MokListNode *build_mok_list(UINT32 num, void *Data, UINTN DataSize) { + MokListNode *list; + EFI_SIGNATURE_LIST *CertList = Data; + EFI_SIGNATURE_DATA *Cert; + EFI_GUID CertType = EfiCertX509Guid; + EFI_GUID HashType = EfiHashSha256Guid; + UINTN dbsize = DataSize; + UINTN count = 0; + + list = AllocatePool(sizeof(MokListNode) * num); + + if (!list) { + Print(L"Unable to allocate MOK list\n"); + return NULL; + } + + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { + if ((CompareGuid (&CertList->SignatureType, &CertType) != 0) && + (CompareGuid (&CertList->SignatureType, &HashType) != 0)) { + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *) CertList + + CertList->SignatureListSize); + continue; + } + + if ((CompareGuid (&CertList->SignatureType, &HashType) == 0) && + (CertList->SignatureSize != 48)) { + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *)((UINT8 *) CertList + + CertList->SignatureListSize); + continue; + } + + Cert = (EFI_SIGNATURE_DATA *) (((UINT8 *) CertList) + + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); + + list[count].MokSize = CertList->SignatureSize; + list[count].Mok = (void *)Cert->SignatureData; + + count++; + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + + CertList->SignatureListSize); + } + + return list; +} + +static void print_x509_name (X509_NAME *X509Name, CHAR16 *name) +{ + char *str; + + str = X509_NAME_oneline(X509Name, NULL, 0); + if (str) { + Print(L" %s:\n %a\n", name, str); + OPENSSL_free(str); + } +} + +static const char *mon[12]= { +"Jan","Feb","Mar","Apr","May","Jun", +"Jul","Aug","Sep","Oct","Nov","Dec" +}; + +static void print_x509_GENERALIZEDTIME_time (ASN1_TIME *time, CHAR16 *time_string) +{ + char *v; + int gmt = 0; + int i; + int y = 0,M = 0,d = 0,h = 0,m = 0,s = 0; + char *f = NULL; + int f_len = 0; + + i=time->length; + v=(char *)time->data; + + if (i < 12) + goto error; + + if (v[i-1] == 'Z') + gmt=1; + + for (i=0; i<12; i++) { + if ((v[i] > '9') || (v[i] < '0')) + goto error; + } + + y = (v[0]-'0')*1000+(v[1]-'0')*100 + (v[2]-'0')*10+(v[3]-'0'); + M = (v[4]-'0')*10+(v[5]-'0'); + + if ((M > 12) || (M < 1)) + goto error; + + d = (v[6]-'0')*10+(v[7]-'0'); + h = (v[8]-'0')*10+(v[9]-'0'); + m = (v[10]-'0')*10+(v[11]-'0'); + + if (time->length >= 14 && + (v[12] >= '0') && (v[12] <= '9') && + (v[13] >= '0') && (v[13] <= '9')) { + s = (v[12]-'0')*10+(v[13]-'0'); + /* Check for fractions of seconds. */ + if (time->length >= 15 && v[14] == '.') { + int l = time->length; + f = &v[14]; /* The decimal point. */ + f_len = 1; + while (14 + f_len < l && f[f_len] >= '0' && + f[f_len] <= '9') + ++f_len; + } + } + + SPrint(time_string, 0, L"%a %2d %02d:%02d:%02d%.*a %d%a", + mon[M-1], d, h, m, s, f_len, f, y, (gmt)?" GMT":""); +error: + return; +} + +static void print_x509_UTCTIME_time (ASN1_TIME *time, CHAR16 *time_string) +{ + char *v; + int gmt=0; + int i; + int y = 0,M = 0,d = 0,h = 0,m = 0,s = 0; + + i=time->length; + v=(char *)time->data; + + if (i < 10) + goto error; + + if (v[i-1] == 'Z') + gmt=1; + + for (i=0; i<10; i++) + if ((v[i] > '9') || (v[i] < '0')) + goto error; + + y = (v[0]-'0')*10+(v[1]-'0'); + + if (y < 50) + y+=100; + + M = (v[2]-'0')*10+(v[3]-'0'); + + if ((M > 12) || (M < 1)) + goto error; + + d = (v[4]-'0')*10+(v[5]-'0'); + h = (v[6]-'0')*10+(v[7]-'0'); + m = (v[8]-'0')*10+(v[9]-'0'); + + if (time->length >=12 && + (v[10] >= '0') && (v[10] <= '9') && + (v[11] >= '0') && (v[11] <= '9')) + s = (v[10]-'0')*10+(v[11]-'0'); + + SPrint(time_string, 0, L"%a %2d %02d:%02d:%02d %d%a", + mon[M-1], d, h, m, s, y+1900, (gmt)?" GMT":""); +error: + return; +} + +static void print_x509_time (ASN1_TIME *time, CHAR16 *name) +{ + CHAR16 time_string[30]; + + if (time->type == V_ASN1_UTCTIME) { + print_x509_UTCTIME_time(time, time_string); + } else if (time->type == V_ASN1_GENERALIZEDTIME) { + print_x509_GENERALIZEDTIME_time(time, time_string); + } else { + time_string[0] = '\0'; + } + + Print(L" %s:\n %s\n", name, time_string); +} + +static void show_x509_info (X509 *X509Cert) +{ + ASN1_INTEGER *serial; + BIGNUM *bnser; + unsigned char hexbuf[30]; + X509_NAME *X509Name; + ASN1_TIME *time; + + serial = X509_get_serialNumber(X509Cert); + if (serial) { + int i, n; + bnser = ASN1_INTEGER_to_BN(serial, NULL); + n = BN_bn2bin(bnser, hexbuf); + Print(L" Serial Number:\n "); + for (i = 0; i < n-1; i++) { + Print(L"%02x:", hexbuf[i]); + } + Print(L"%02x\n", hexbuf[n-1]); + } + + X509Name = X509_get_issuer_name(X509Cert); + if (X509Name) { + print_x509_name(X509Name, L"Issuer"); + } + + X509Name = X509_get_subject_name(X509Cert); + if (X509Name) { + print_x509_name(X509Name, L"Subject"); + } + + time = X509_get_notBefore(X509Cert); + if (time) { + print_x509_time(time, L"Validity from"); + } + + time = X509_get_notAfter(X509Cert); + if (time) { + print_x509_time(time, L"Validity till"); + } +} + +static void show_mok_info (void *Mok, UINTN MokSize) +{ + EFI_STATUS efi_status; + UINT8 hash[SHA1_DIGEST_SIZE]; + unsigned int i; + X509 *X509Cert; + + if (!Mok || MokSize == 0) + return; + + if (MokSize != 48) { + if (X509ConstructCertificate(Mok, MokSize, + (UINT8 **) &X509Cert) && X509Cert != NULL) { + show_x509_info(X509Cert); + X509_free(X509Cert); + } else { + Print(L" Not a valid X509 certificate: %x\n\n", + ((UINT32 *)Mok)[0]); + return; + } + + efi_status = get_sha1sum(Mok, MokSize, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to compute MOK fingerprint\n"); + return; + } + + Print(L" Fingerprint (SHA1):\n "); + for (i = 0; i < SHA1_DIGEST_SIZE; i++) { + Print(L" %02x", hash[i]); + if (i % 10 == 9) + Print(L"\n "); + } + } else { + Print(L"SHA256 hash:\n "); + for (i = 0; i < SHA256_DIGEST_SIZE; i++) { + Print(L" %02x", ((UINT8 *)Mok)[i]); + if (i % 10 == 9) + Print(L"\n "); + } + Print(L"\n"); + } + + Print(L"\n"); +} + +static INTN get_number () +{ + EFI_INPUT_KEY input_key; + CHAR16 input[10]; + int count = 0; + + do { + input_key = get_keystroke(); + + if ((input_key.UnicodeChar < '0' || + input_key.UnicodeChar > '9' || + count >= 10) && + input_key.UnicodeChar != CHAR_BACKSPACE) { + continue; + } + + if (count == 0 && input_key.UnicodeChar == CHAR_BACKSPACE) + continue; + + Print(L"%c", input_key.UnicodeChar); + + if (input_key.UnicodeChar == CHAR_BACKSPACE) { + input[--count] = '\0'; + continue; + } + + input[count++] = input_key.UnicodeChar; + } while (input_key.UnicodeChar != CHAR_CARRIAGE_RETURN); + + if (count == 0) + return -1; + + input[count] = '\0'; + + return (INTN)Atoi(input); +} + +static UINT8 list_keys (void *MokNew, UINTN MokNewSize) +{ + UINT32 MokNum = 0; + MokListNode *keys = NULL; + INTN key_num = 0; + UINT8 initial = 1; + EFI_SIGNATURE_LIST *CertList = MokNew; + EFI_GUID CertType = EfiCertX509Guid; + EFI_GUID HashType = EfiHashSha256Guid; + UINTN dbsize = MokNewSize; + + if (MokNewSize < (sizeof(EFI_SIGNATURE_LIST) + + sizeof(EFI_SIGNATURE_DATA))) { + Print(L"No keys\n"); + Pause(); + return 0; + } + + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { + if ((CompareGuid (&CertList->SignatureType, &CertType) != 0) && + (CompareGuid (&CertList->SignatureType, &HashType) != 0)) { + Print(L"Doesn't look like a key or hash\n"); + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + + CertList->SignatureListSize); + continue; + } + + if ((CompareGuid (&CertList->SignatureType, &CertType) != 0) && + (CertList->SignatureSize != 48)) { + Print(L"Doesn't look like a valid hash\n"); + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + + CertList->SignatureListSize); + continue; + } + + MokNum++; + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + + CertList->SignatureListSize); + } + + keys = build_mok_list(MokNum, MokNew, MokNewSize); + + if (!keys) { + Print(L"Failed to construct key list in MokNew\n"); + return 0; + } + + do { + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + Print(L"Input the key number to show the details of the key or\n" + L"type \'0\' to continue\n\n"); + Print(L"%d key(s) in the new key list\n\n", MokNum); + + if (key_num > MokNum) { + Print(L"[Key %d]\n", key_num); + Print(L"No such key\n\n"); + } else if (initial != 1 && key_num > 0){ + Print(L"[Key %d]\n", key_num); + show_mok_info(keys[key_num-1].Mok, keys[key_num-1].MokSize); + } + + Print(L"Key Number: "); + + key_num = get_number(); + + Print(L"\n\n"); + + if (key_num == -1) + continue; + + initial = 0; + } while (key_num != 0); + + FreePool(keys); + + return 1; +} + +static UINT8 get_line (UINT32 *length, CHAR16 *line, UINT32 line_max, UINT8 show) +{ + EFI_INPUT_KEY key; + int count = 0; + + do { + key = get_keystroke(); + + if ((count >= line_max && + key.UnicodeChar != CHAR_BACKSPACE) || + key.UnicodeChar == CHAR_NULL || + key.UnicodeChar == CHAR_TAB || + key.UnicodeChar == CHAR_LINEFEED || + key.UnicodeChar == CHAR_CARRIAGE_RETURN) { + continue; + } + + if (count == 0 && key.UnicodeChar == CHAR_BACKSPACE) { + continue; + } else if (key.UnicodeChar == CHAR_BACKSPACE) { + if (show) { + Print(L"\b"); + } + line[--count] = '\0'; + continue; + } + + if (show) { + Print(L"%c", key.UnicodeChar); + } + + line[count++] = key.UnicodeChar; + } while (key.UnicodeChar != CHAR_CARRIAGE_RETURN); + Print(L"\n"); + + *length = count; + + return 1; +} + +static EFI_STATUS compute_pw_hash (void *MokNew, UINTN MokNewSize, CHAR16 *password, + UINT32 pw_length, UINT8 *hash) +{ + EFI_STATUS status; + unsigned int ctxsize; + void *ctx = NULL; + + ctxsize = Sha256GetContextSize(); + ctx = AllocatePool(ctxsize); + + if (!ctx) { + Print(L"Unable to allocate memory for hash context\n"); + return EFI_OUT_OF_RESOURCES; + } + + if (!Sha256Init(ctx)) { + Print(L"Unable to initialise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + if (MokNew && MokNewSize) { + if (!(Sha256Update(ctx, MokNew, MokNewSize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + } + + if (!(Sha256Update(ctx, password, pw_length * sizeof(CHAR16)))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + if (!(Sha256Final(ctx, hash))) { + Print(L"Unable to finalise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + status = EFI_SUCCESS; +done: + return status; +} + +static EFI_STATUS store_keys (void *MokNew, UINTN MokNewSize, int authenticate) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINT8 hash[SHA256_DIGEST_SIZE]; + UINT8 auth[SHA256_DIGEST_SIZE]; + UINTN auth_size; + UINT32 attributes; + CHAR16 password[PASSWORD_MAX]; + UINT32 pw_length; + UINT8 fail_count = 0; + + if (authenticate) { + auth_size = SHA256_DIGEST_SIZE; + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokAuth", + &shim_lock_guid, + &attributes, &auth_size, auth); + + + if (efi_status != EFI_SUCCESS || auth_size != SHA256_DIGEST_SIZE) { + Print(L"Failed to get MokAuth %d\n", efi_status); + return efi_status; + } + + while (fail_count < 3) { + Print(L"Password(%d-%d characters): ", + PASSWORD_MIN, PASSWORD_MAX); + get_line(&pw_length, password, PASSWORD_MAX, 0); + + if (pw_length < 8) { + Print(L"At least %d characters for the password\n", + PASSWORD_MIN); + } + + efi_status = compute_pw_hash(MokNew, MokNewSize, password, + pw_length, hash); + + if (efi_status != EFI_SUCCESS) { + return efi_status; + } + + if (CompareMem(auth, hash, SHA256_DIGEST_SIZE) != 0) { + Print(L"Password doesn't match\n"); + fail_count++; + } else { + break; + } + } + + if (fail_count >= 3) + return EFI_ACCESS_DENIED; + } + + if (!MokNewSize) { + /* Delete MOK */ + efi_status = uefi_call_wrapper(RT->SetVariable, 5, L"MokList", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE + | EFI_VARIABLE_BOOTSERVICE_ACCESS, + 0, NULL); + } else { + /* Write new MOK */ + efi_status = uefi_call_wrapper(RT->SetVariable, 5, L"MokList", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE + | EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_APPEND_WRITE, + MokNewSize, MokNew); + } + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set variable %d\n", efi_status); + return efi_status; + } + + return EFI_SUCCESS; +} + +static UINTN mok_enrollment_prompt (void *MokNew, UINTN MokNewSize, int auth) { + CHAR16 line[1]; + UINT32 length; + EFI_STATUS efi_status; + + do { + if (!list_keys(MokNew, MokNewSize)) { + return 0; + } + + Print(L"Enroll the key(s)? (y/n): "); + + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + efi_status = store_keys(MokNew, MokNewSize, auth); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to enroll keys\n"); + return -1; + } + return 0; + } + } while (line[0] != 'N' && line[0] != 'n'); + return -1; +} + +static INTN mok_enrollment_prompt_callback (void *MokNew, void *data2, + void *data3) +{ + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + return mok_enrollment_prompt(MokNew, (UINTN)data2, TRUE); +} + +static INTN mok_deletion_prompt (void *MokNew, void *data2, void *data3) { + CHAR16 line[1]; + UINT32 length; + EFI_STATUS efi_status; + + Print(L"Erase all stored keys? (y/N): "); + + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + efi_status = store_keys(NULL, 0, TRUE); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to erase keys\n"); + return -1; + } + } + + return 0; +} + +static INTN mok_sb_prompt (void *MokSB, void *data2, void *data3) { + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINTN MokSBSize = (UINTN)data2; + MokSBvar *var = MokSB; + CHAR16 pass1, pass2, pass3; + UINT8 fail_count = 0; + UINT32 length; + CHAR16 line[1]; + UINT8 sbval = 1; + UINT8 pos1, pos2, pos3; + + if (MokSBSize != sizeof(MokSBvar)) { + Print(L"Invalid MokSB variable contents\n"); + return -1; + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + while (fail_count < 3) { + RandomBytes (&pos1, sizeof(pos1)); + pos1 = (pos1 % var->PWLen); + + do { + RandomBytes (&pos2, sizeof(pos2)); + pos2 = (pos2 % var->PWLen); + } while (pos2 == pos1); + + do { + RandomBytes (&pos3, sizeof(pos3)); + pos3 = (pos3 % var->PWLen) ; + } while (pos3 == pos2 || pos3 == pos1); + + Print(L"Enter password character %d: ", pos1 + 1); + get_line(&length, &pass1, 1, 0); + + Print(L"Enter password character %d: ", pos2 + 1); + get_line(&length, &pass2, 1, 0); + + Print(L"Enter password character %d: ", pos3 + 1); + get_line(&length, &pass3, 1, 0); + + if (pass1 != var->Password[pos1] || + pass2 != var->Password[pos2] || + pass3 != var->Password[pos3]) { + Print(L"Invalid character\n"); + fail_count++; + } else { + break; + } + } + + if (fail_count >= 3) { + Print(L"Password limit reached\n"); + return -1; + } + + if (var->MokSBState == 0) { + Print(L"Disable Secure Boot? (y/n): "); + } else { + Print(L"Enable Secure Boot? (y/n): "); + } + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + if (var->MokSBState == 0) { + efi_status = uefi_call_wrapper(RT->SetVariable, + 5, L"MokSBState", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + 1, &sbval); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set Secure Boot state\n"); + return -1; + } + } else { + LibDeleteVariable(L"MokSBState", + &shim_lock_guid); + } + + LibDeleteVariable(L"MokSB", &shim_lock_guid); + + Print(L"Press a key to reboot system\n"); + Pause(); + uefi_call_wrapper(RT->ResetSystem, 4, EfiResetWarm, + EFI_SUCCESS, 0, NULL); + Print(L"Failed to reboot\n"); + return -1; + } + } while (line[0] != 'N' && line[0] != 'n'); + + return -1; +} + + +static INTN mok_pw_prompt (void *MokPW, void *data2, void *data3) { + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINTN MokPWSize = (UINTN)data2; + UINT8 fail_count = 0; + UINT8 hash[SHA256_DIGEST_SIZE]; + CHAR16 password[PASSWORD_MAX]; + UINT32 length; + CHAR16 line[1]; + + if (MokPWSize != SHA256_DIGEST_SIZE) { + Print(L"Invalid MokPW variable contents\n"); + return -1; + } + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + SetMem(hash, SHA256_DIGEST_SIZE, 0); + + if (CompareMem(MokPW, hash, SHA256_DIGEST_SIZE) == 0) { + Print(L"Clear MOK password? (y/n): "); + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + LibDeleteVariable(L"MokPWStore", &shim_lock_guid); + LibDeleteVariable(L"MokPW", &shim_lock_guid); + } + } while (line[0] != 'N' && line[0] != 'n'); + + return 0; + } + + while (fail_count < 3) { + Print(L"Confirm MOK passphrase: "); + get_line(&length, password, PASSWORD_MAX, 0); + + if ((length < PASSWORD_MIN) || (length > PASSWORD_MAX)) { + Print(L"Invalid password length\n"); + fail_count++; + continue; + } + + efi_status = compute_pw_hash(NULL, 0, password, length, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to generate password hash\n"); + fail_count++; + continue; + } + + if (CompareMem(MokPW, hash, SHA256_DIGEST_SIZE) != 0) { + Print(L"Password doesn't match\n"); + fail_count++; + continue; + } + + break; + } + + if (fail_count >= 3) { + Print(L"Password limit reached\n"); + return -1; + } + + Print(L"Set MOK password? (y/n): "); + + do { + get_line (&length, line, 1, 1); + + if (line[0] == 'Y' || line[0] == 'y') { + efi_status = uefi_call_wrapper(RT->SetVariable, 5, + L"MokPWStore", + &shim_lock_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS, + MokPWSize, MokPW); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set MOK password\n"); + return -1; + } + + LibDeleteVariable(L"MokPW", &shim_lock_guid); + + Print(L"Press a key to reboot system\n"); + Pause(); + uefi_call_wrapper(RT->ResetSystem, 4, EfiResetWarm, + EFI_SUCCESS, 0, NULL); + Print(L"Failed to reboot\n"); + return -1; + } + } while (line[0] != 'N' && line[0] != 'n'); + + return 0; +} + +static UINTN draw_menu (CHAR16 *header, UINTN lines, struct menu_item *items, + UINTN count) { + UINTN i; + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_WHITE | EFI_BACKGROUND_BLACK); + + Print(L"%s UEFI key management\n\n", SHIM_VENDOR); + + if (header) + Print(L"%s", header); + + for (i = 0; i < count; i++) { + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + items[i].colour | EFI_BACKGROUND_BLACK); + Print(L" %s\n", items[i].text); + } + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, 0); + uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, TRUE); + + return 2 + lines; +} + +static void free_menu (struct menu_item *items, UINTN count) { + UINTN i; + + for (i=0; i<count; i++) { + if (items[i].text) + FreePool(items[i].text); + } + + FreePool(items); +} + +static void update_time (UINTN position, UINTN timeout) +{ + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, + position); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_BLACK | EFI_BACKGROUND_BLACK); + + Print(L" ", timeout); + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, + position); + + uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, + EFI_WHITE | EFI_BACKGROUND_BLACK); + + if (timeout > 1) + Print(L"Booting in %d seconds\n", timeout); + else if (timeout) + Print(L"Booting in %d second\n", timeout); +} + +static void run_menu (CHAR16 *header, UINTN lines, struct menu_item *items, + UINTN count, UINTN timeout) { + UINTN index, pos = 0, wait = 0, offset; + EFI_INPUT_KEY key; + EFI_STATUS status; + INTN ret; + + if (timeout) + wait = 10000000; + + offset = draw_menu (header, lines, items, count); + + while (1) { + update_time(count + offset + 1, timeout); + + uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, + 0, pos + offset); + status = WaitForSingleEvent(ST->ConIn->WaitForKey, wait); + + if (status == EFI_TIMEOUT) { + timeout--; + if (!timeout) { + free_menu(items, count); + return; + } + continue; + } + + wait = 0; + timeout = 0; + + uefi_call_wrapper(BS->WaitForEvent, 3, 1, + &ST->ConIn->WaitForKey, &index); + uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, + &key); + + switch(key.ScanCode) { + case SCAN_UP: + if (pos == 0) + continue; + pos--; + continue; + break; + case SCAN_DOWN: + if (pos == (count - 1)) + continue; + pos++; + continue; + break; + } + + switch(key.UnicodeChar) { + case CHAR_LINEFEED: + case CHAR_CARRIAGE_RETURN: + if (items[pos].callback == NULL) { + free_menu(items, count); + return; + } + + ret = items[pos].callback(items[pos].data, + items[pos].data2, + items[pos].data3); + if (ret < 0) { + Print(L"Press a key to continue\n"); + Pause(); + } + draw_menu (header, lines, items, count); + pos = 0; + break; + } + } +} + +static UINTN verify_certificate(void *cert, UINTN size) +{ + X509 *X509Cert; + if (!cert || size == 0) + return FALSE; + + if (!(X509ConstructCertificate(cert, size, (UINT8 **) &X509Cert)) || + X509Cert == NULL) { + Print(L"Invalid X509 certificate\n"); + Pause(); + return FALSE; + } + + X509_free(X509Cert); + return TRUE; +} + +static INTN file_callback (void *data, void *data2, void *data3) { + EFI_FILE_INFO *buffer = NULL; + UINTN buffersize = 0, mokbuffersize; + EFI_STATUS status; + EFI_FILE *file; + CHAR16 *filename = data; + EFI_FILE *parent = data2; + BOOLEAN hash = !!data3; + EFI_GUID file_info_guid = EFI_FILE_INFO_ID; + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_SIGNATURE_LIST *CertList; + EFI_SIGNATURE_DATA *CertData; + void *mokbuffer = NULL; + + status = uefi_call_wrapper(parent->Open, 5, parent, &file, filename, + EFI_FILE_MODE_READ, 0); + + if (status != EFI_SUCCESS) + return 1; + + status = uefi_call_wrapper(file->GetInfo, 4, file, &file_info_guid, + &buffersize, buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(file->GetInfo, 4, file, + &file_info_guid, &buffersize, + buffer); + } + + if (!buffer) + return 0; + + buffersize = buffer->FileSize; + + if (hash) { + void *binary; + UINT8 sha256[SHA256_DIGEST_SIZE]; + UINT8 sha1[SHA1_DIGEST_SIZE]; + SHIM_LOCK *shim_lock; + EFI_GUID shim_guid = SHIM_LOCK_GUID; + PE_COFF_LOADER_IMAGE_CONTEXT context; + + status = LibLocateProtocol(&shim_guid, (VOID **)&shim_lock); + + if (status != EFI_SUCCESS) + goto out; + + mokbuffersize = sizeof(EFI_SIGNATURE_LIST) + sizeof(EFI_GUID) + + SHA256_DIGEST_SIZE; + + mokbuffer = AllocatePool(mokbuffersize); + + if (!mokbuffer) + goto out; + + binary = AllocatePool(buffersize); + + status = uefi_call_wrapper(file->Read, 3, file, &buffersize, + binary); + + if (status != EFI_SUCCESS) + goto out; + + status = shim_lock->Context(binary, buffersize, &context); + + if (status != EFI_SUCCESS) + goto out; + + status = shim_lock->Hash(binary, buffersize, &context, sha256, + sha1); + + if (status != EFI_SUCCESS) + goto out; + + CertList = mokbuffer; + CertList->SignatureType = EfiHashSha256Guid; + CertList->SignatureSize = 16 + SHA256_DIGEST_SIZE; + CertData = (EFI_SIGNATURE_DATA *)(((UINT8 *)mokbuffer) + + sizeof(EFI_SIGNATURE_LIST)); + CopyMem(CertData->SignatureData, sha256, SHA256_DIGEST_SIZE); + } else { + mokbuffersize = buffersize + sizeof(EFI_SIGNATURE_LIST) + + sizeof(EFI_GUID); + mokbuffer = AllocatePool(mokbuffersize); + + if (!mokbuffer) + goto out; + + CertList = mokbuffer; + CertList->SignatureType = EfiCertX509Guid; + CertList->SignatureSize = 16 + buffersize; + status = uefi_call_wrapper(file->Read, 3, file, &buffersize, + mokbuffer + sizeof(EFI_SIGNATURE_LIST) + 16); + + if (status != EFI_SUCCESS) + goto out; + CertData = (EFI_SIGNATURE_DATA *)(((UINT8 *)mokbuffer) + + sizeof(EFI_SIGNATURE_LIST)); + } + + CertList->SignatureListSize = mokbuffersize; + CertList->SignatureHeaderSize = 0; + CertData->SignatureOwner = shim_lock_guid; + + if (!hash) { + if (!verify_certificate(CertData->SignatureData, buffersize)) + goto out; + } + + mok_enrollment_prompt(mokbuffer, mokbuffersize, FALSE); +out: + if (buffer) + FreePool(buffer); + + if (mokbuffer) + FreePool(mokbuffer); + + return 0; +} + +static INTN directory_callback (void *data, void *data2, void *data3) { + EFI_FILE_INFO *buffer = NULL; + UINTN buffersize = 0; + EFI_STATUS status; + UINTN dircount = 0, i = 0; + struct menu_item *dircontent; + EFI_FILE *dir; + CHAR16 *filename = data; + EFI_FILE *root = data2; + BOOLEAN hash = !!data3; + + status = uefi_call_wrapper(root->Open, 5, root, &dir, filename, + EFI_FILE_MODE_READ, 0); + + if (status != EFI_SUCCESS) + return 1; + + while (1) { + status = uefi_call_wrapper(dir->Read, 3, dir, &buffersize, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(dir->Read, 3, dir, + &buffersize, buffer); + } + + if (status != EFI_SUCCESS) + return 1; + + if (!buffersize) + break; + + if ((StrCmp(buffer->FileName, L".") == 0) || + (StrCmp(buffer->FileName, L"..") == 0)) + continue; + + dircount++; + + FreePool(buffer); + buffersize = 0; + } + + dircount++; + + dircontent = AllocatePool(sizeof(struct menu_item) * dircount); + + dircontent[0].text = StrDuplicate(L".."); + dircontent[0].callback = NULL; + dircontent[0].colour = EFI_YELLOW; + i++; + + uefi_call_wrapper(dir->SetPosition, 2, dir, 0); + + while (1) { + status = uefi_call_wrapper(dir->Read, 3, dir, &buffersize, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(dir->Read, 3, dir, + &buffersize, buffer); + } + + if (status != EFI_SUCCESS) + return 1; + + if (!buffersize) + break; + + if ((StrCmp(buffer->FileName, L".") == 0) || + (StrCmp(buffer->FileName, L"..") == 0)) + continue; + + if (buffer->Attribute & EFI_FILE_DIRECTORY) { + dircontent[i].text = StrDuplicate(buffer->FileName); + dircontent[i].callback = directory_callback; + dircontent[i].data = dircontent[i].text; + dircontent[i].data2 = dir; + dircontent[i].data3 = data3; + dircontent[i].colour = EFI_YELLOW; + } else { + dircontent[i].text = StrDuplicate(buffer->FileName); + dircontent[i].callback = file_callback; + dircontent[i].data = dircontent[i].text; + dircontent[i].data2 = dir; + dircontent[i].data3 = data3; + dircontent[i].colour = EFI_WHITE; + } + + i++; + FreePool(buffer); + buffersize = 0; + buffer = NULL; + } + + if (hash) + run_menu(HASH_STRING, 2, dircontent, dircount, 0); + else + run_menu(CERT_STRING, 2, dircontent, dircount, 0); + + return 0; +} + +static INTN filesystem_callback (void *data, void *data2, void *data3) { + EFI_FILE_INFO *buffer = NULL; + UINTN buffersize = 0; + EFI_STATUS status; + UINTN dircount = 0, i = 0; + struct menu_item *dircontent; + EFI_FILE *root = data; + BOOLEAN hash = !!data3; + + uefi_call_wrapper(root->SetPosition, 2, root, 0); + + while (1) { + status = uefi_call_wrapper(root->Read, 3, root, &buffersize, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(root->Read, 3, root, + &buffersize, buffer); + } + + if (status != EFI_SUCCESS) + return 1; + + if (!buffersize) + break; + + if ((StrCmp(buffer->FileName, L".") == 0) || + (StrCmp(buffer->FileName, L"..") == 0)) + continue; + + dircount++; + + FreePool(buffer); + buffersize = 0; + } + + dircount++; + + dircontent = AllocatePool(sizeof(struct menu_item) * dircount); + + dircontent[0].text = StrDuplicate(L"Return to filesystem list"); + dircontent[0].callback = NULL; + dircontent[0].colour = EFI_YELLOW; + i++; + + uefi_call_wrapper(root->SetPosition, 2, root, 0); + + while (1) { + status = uefi_call_wrapper(root->Read, 3, root, &buffersize, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(root->Read, 3, root, + &buffersize, buffer); + } + + if (status != EFI_SUCCESS) + return 1; + + if (!buffersize) + break; + + if ((StrCmp(buffer->FileName, L".") == 0) || + (StrCmp(buffer->FileName, L"..") == 0)) + continue; + + if (buffer->Attribute & EFI_FILE_DIRECTORY) { + dircontent[i].text = StrDuplicate(buffer->FileName); + dircontent[i].callback = directory_callback; + dircontent[i].data = dircontent[i].text; + dircontent[i].data2 = root; + dircontent[i].data3 = data3; + dircontent[i].colour = EFI_YELLOW; + } else { + dircontent[i].text = StrDuplicate(buffer->FileName); + dircontent[i].callback = file_callback; + dircontent[i].data = dircontent[i].text; + dircontent[i].data2 = root; + dircontent[i].data3 = data3; + dircontent[i].colour = EFI_WHITE; + } + + i++; + FreePool(buffer); + buffer = NULL; + buffersize = 0; + } + + if (hash) + run_menu(HASH_STRING, 2, dircontent, dircount, 0); + else + run_menu(CERT_STRING, 2, dircontent, dircount, 0); + + return 0; +} + +static INTN find_fs (void *data, void *data2, void *data3) { + EFI_GUID fs_guid = SIMPLE_FILE_SYSTEM_PROTOCOL; + UINTN count, i; + UINTN OldSize, NewSize; + EFI_HANDLE *filesystem_handles = NULL; + struct menu_item *filesystems; + BOOLEAN hash = !!data3; + + uefi_call_wrapper(BS->LocateHandleBuffer, 5, ByProtocol, &fs_guid, + NULL, &count, &filesystem_handles); + + if (!count || !filesystem_handles) { + Print(L"No filesystems?\n"); + return 1; + } + + count++; + + filesystems = AllocatePool(sizeof(struct menu_item) * count); + + filesystems[0].text = StrDuplicate(L"Exit"); + filesystems[0].callback = NULL; + filesystems[0].colour = EFI_YELLOW; + + for (i=1; i<count; i++) { + EFI_HANDLE fs = filesystem_handles[i-1]; + EFI_FILE_IO_INTERFACE *fs_interface; + EFI_DEVICE_PATH *path; + EFI_FILE *root; + EFI_STATUS status; + CHAR16 *VolumeLabel = NULL; + EFI_FILE_SYSTEM_INFO *buffer = NULL; + UINTN buffersize = 0; + EFI_GUID file_info_guid = EFI_FILE_INFO_ID; + + status = uefi_call_wrapper(BS->HandleProtocol, 3, fs, &fs_guid, + (void **)&fs_interface); + + if (status != EFI_SUCCESS || !fs_interface) + continue; + + path = DevicePathFromHandle(fs); + + status = uefi_call_wrapper(fs_interface->OpenVolume, 2, + fs_interface, &root); + + if (status != EFI_SUCCESS || !root) + continue; + + status = uefi_call_wrapper(root->GetInfo, 4, root, + &file_info_guid, &buffersize, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + buffer = AllocatePool(buffersize); + status = uefi_call_wrapper(root->GetInfo, 4, root, + &file_info_guid, + &buffersize, buffer); + } + + if (status == EFI_SUCCESS) + VolumeLabel = buffer->VolumeLabel; + + if (path) + filesystems[i].text = DevicePathToStr(path); + else + filesystems[i].text = StrDuplicate(L"Unknown device\n"); + if (VolumeLabel) { + OldSize = (StrLen(filesystems[i].text) + 1) * sizeof(CHAR16); + NewSize = OldSize + StrLen(VolumeLabel) * sizeof(CHAR16); + filesystems[i].text = ReallocatePool(filesystems[i].text, + OldSize, NewSize); + StrCat(filesystems[i].text, VolumeLabel); + } + + if (buffersize) + FreePool(buffer); + + filesystems[i].data = root; + filesystems[i].data2 = NULL; + filesystems[i].data3 = data3; + filesystems[i].callback = filesystem_callback; + filesystems[i].colour = EFI_YELLOW; + } + + uefi_call_wrapper(BS->FreePool, 1, filesystem_handles); + + if (hash) + run_menu(HASH_STRING, 2, filesystems, count, 0); + else + run_menu(CERT_STRING, 2, filesystems, count, 0); + + return 0; +} + +static BOOLEAN verify_pw(void) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + CHAR16 password[PASSWORD_MAX]; + UINT8 fail_count = 0; + UINT8 hash[SHA256_DIGEST_SIZE]; + UINT8 pwhash[SHA256_DIGEST_SIZE]; + UINTN size = SHA256_DIGEST_SIZE; + UINT32 length; + UINT32 attributes; + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokPWStore", + &shim_lock_guid, &attributes, &size, + pwhash); + + /* + * If anything can attack the password it could just set it to a + * known value, so there's no safety advantage in failing to validate + * purely because of a failure to read the variable + */ + if (efi_status != EFI_SUCCESS) + return TRUE; + + if (attributes & EFI_VARIABLE_RUNTIME_ACCESS) + return TRUE; + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + while (fail_count < 3) { + Print(L"Enter MOK password: "); + get_line(&length, password, PASSWORD_MAX, 0); + + if (length < PASSWORD_MIN || length > PASSWORD_MAX) { + Print(L"Invalid password length\n"); + fail_count++; + continue; + } + + efi_status = compute_pw_hash(NULL, 0, password, length, hash); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to generate password hash\n"); + fail_count++; + continue; + } + + if (CompareMem(pwhash, hash, SHA256_DIGEST_SIZE) != 0) { + Print(L"Password doesn't match\n"); + fail_count++; + continue; + } + + return TRUE; + } + + Print(L"Password limit reached\n"); + return FALSE; +} + +static EFI_STATUS enter_mok_menu(EFI_HANDLE image_handle, void *MokNew, + UINTN MokNewSize, void *MokSB, + UINTN MokSBSize, void *MokPW, UINTN MokPWSize) +{ + struct menu_item *menu_item; + UINT32 MokAuth = 0; + UINTN menucount = 3, i = 0; + EFI_STATUS efi_status; + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + UINT8 auth[SHA256_DIGEST_SIZE]; + UINTN auth_size = SHA256_DIGEST_SIZE; + UINT32 attributes; + + if (verify_pw() == FALSE) + return EFI_ACCESS_DENIED; + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, L"MokAuth", + &shim_lock_guid, + &attributes, &auth_size, auth); + + if ((efi_status == EFI_SUCCESS) && (auth_size == SHA256_DIGEST_SIZE)) + MokAuth = 1; + + if (MokNew || MokAuth) + menucount++; + + if (MokSB) + menucount++; + + if (MokPW) + menucount++; + + menu_item = AllocateZeroPool(sizeof(struct menu_item) * menucount); + + if (!menu_item) + return EFI_OUT_OF_RESOURCES; + + menu_item[i].text = StrDuplicate(L"Continue boot"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = NULL; + + i++; + + if (MokNew || MokAuth) { + if (!MokNew) { + menu_item[i].text = StrDuplicate(L"Delete MOK"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_deletion_prompt; + } else { + menu_item[i].text = StrDuplicate(L"Enroll MOK"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].data = MokNew; + menu_item[i].data2 = (void *)MokNewSize; + menu_item[i].callback = mok_enrollment_prompt_callback; + } + i++; + } + + if (MokSB) { + menu_item[i].text = StrDuplicate(L"Change Secure Boot state"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_sb_prompt; + menu_item[i].data = MokSB; + menu_item[i].data2 = (void *)MokSBSize; + i++; + } + + if (MokPW) { + menu_item[i].text = StrDuplicate(L"Set MOK password"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = mok_pw_prompt; + menu_item[i].data = MokPW; + menu_item[i].data2 = (void *)MokPWSize; + i++; + } + + menu_item[i].text = StrDuplicate(L"Enroll key from disk"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = find_fs; + menu_item[i].data3 = (void *)FALSE; + + i++; + + menu_item[i].text = StrDuplicate(L"Enroll hash from disk"); + menu_item[i].colour = EFI_WHITE; + menu_item[i].callback = find_fs; + menu_item[i].data3 = (void *)TRUE; + + i++; + + run_menu(NULL, 0, menu_item, menucount, 10); + + uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); + + return 0; +} + +static EFI_STATUS check_mok_request(EFI_HANDLE image_handle) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + UINTN MokNewSize = 0, MokSBSize = 0, MokPWSize = 0; + void *MokNew = NULL; + void *MokSB = NULL; + void *MokPW = NULL; + + MokNew = LibGetVariableAndSize(L"MokNew", &shim_lock_guid, &MokNewSize); + + MokSB = LibGetVariableAndSize(L"MokSB", &shim_lock_guid, &MokSBSize); + + MokPW = LibGetVariableAndSize(L"MokPW", &shim_lock_guid, &MokPWSize); + + enter_mok_menu(image_handle, MokNew, MokNewSize, MokSB, MokSBSize, + MokPW, MokPWSize); + + if (MokNew) { + if (LibDeleteVariable(L"MokNew", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to delete MokNew\n"); + } + FreePool (MokNew); + } + + if (MokSB) { + if (LibDeleteVariable(L"MokSB", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to delete MokSB\n"); + } + FreePool (MokNew); + } + + if (MokPW) { + if (LibDeleteVariable(L"MokPW", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to delete MokPW\n"); + } + FreePool (MokNew); + } + + LibDeleteVariable(L"MokAuth", &shim_lock_guid); + + return EFI_SUCCESS; +} + +static EFI_STATUS setup_rand (void) +{ + EFI_TIME time; + EFI_STATUS efi_status; + UINT64 seed; + BOOLEAN status; + + efi_status = uefi_call_wrapper(RT->GetTime, 2, &time, NULL); + + if (efi_status != EFI_SUCCESS) + return efi_status; + + seed = ((UINT64)time.Year << 48) | ((UINT64)time.Month << 40) | + ((UINT64)time.Day << 32) | ((UINT64)time.Hour << 24) | + ((UINT64)time.Minute << 16) | ((UINT64)time.Second << 8) | + ((UINT64)time.Daylight); + + status = RandomSeed((UINT8 *)&seed, sizeof(seed)); + + if (!status) + return EFI_ABORTED; + + return EFI_SUCCESS; +} + +EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *systab) +{ + EFI_STATUS efi_status; + + InitializeLib(image_handle, systab); + + setup_rand(); + + efi_status = check_mok_request(image_handle); + + return efi_status; +} diff --git a/MokVars.txt b/MokVars.txt new file mode 100644 index 00000000..74f09083 --- /dev/null +++ b/MokVars.txt @@ -0,0 +1,51 @@ +Variables used by Shim and Mokmanager + +Request variables: + +MokPW: Set by MokUtil when setting a password. A SHA-256 hash of the +UCS-2 representation of the password. The user will be asked to +re-enter the password to confirm. If the hash of the entered password +matches the contents of MokPW, the user will be prompted to copy MokPW +into MokPWState. BS,RT,NV + +MokSB: Set by MokUtil when requesting a change in state of signature +validation. A packed structure as follows: + +typedef struct { + UINT32 MokSBState; + UINT32 PWLen; + CHAR16 Password[PASSWORD_MAX]; +} __attribute__ ((packed)) MokSBvar; + +If MokSBState is 0, the user will be prompted to disable signature +validation. Otherwise, the user will be prompted to enable it. PWLen +is the length of the password, in characters. Password is a UCS-2 +representation of the password. The user will be prompted to enter +three randomly chosen characters from the password. If successful, +they will then be prompted to change the signature validation +according to MokSBState. BS,RT,NV + +MokNew: Set by MokUtil when requesting the addition or removal of keys +from MokList. Is an EFI_SIGNATURE_LIST as described in the UEFI +specification. BS,RT,NV + +MokAuth: A hash dependent upon the contents of MokNew and the sealing +password. The user's password in UCS-2 form should be appended to the +contents of MokNew and a SHA-256 hash generated and stored in MokAuth. +The hash will be regenerated by MokManager after the user is requested +to enter their password to confirm enrolment of the keys. If the hash +matches MokAuth, the user will be prompted to enrol the keys. BS,RT,NV + +State variables: + +MokList: A list of whitelisted keys and hashes. An EFI_SIGNATURE_LIST +as described in the UEFI specification. BS,NV + +MokListRT: A copy of MokList made available to the kernel at runtime. RT + +MokSBState: An 8-bit unsigned integer. If 1, shim will switch to +insecure mode. BS,NV + +MokPWStore: A SHA-256 representation of the password set by the user +via MokPW. The user will be prompted to enter this password in order +to interact with MokManager. @@ -0,0 +1,32 @@ +#if defined(VENDOR_DBX_FILE) + .globl vendor_dbx_size + .data + .align 1 + .type vendor_dbx_size, @object + .size vendor_dbx_size, 4 +vendor_dbx_size: + .long .L0 - vendor_dbx + .globl vendor_dbx + .data + .align 1 + .type vendor_dbx, @object + .size vendor_dbx_size, vendor_dbx_size-vendor_dbx +vendor_dbx: +.incbin VENDOR_DBX_FILE +.L0: +#else + .globl vendor_dbx + .bss + .type vendor_dbx, @object + .size vendor_dbx, 1 +vendor_dbx: + .zero 1 + + .globl vendor_dbx_size + .data + .align 4 + .type vendor_dbx_size, @object + .size vendor_dbx_size, 4 +vendor_dbx_size: + .long 0 +#endif diff --git a/make-certs b/make-certs new file mode 100755 index 00000000..3e9293b2 --- /dev/null +++ b/make-certs @@ -0,0 +1,554 @@ +#!/bin/bash -e +# +# Generate a root CA cert for signing, and then a subject cert. +# Usage: make-certs.sh hostname [user[@domain]] [more ...] +# For testing only, probably still has some bugs in it. +# + +DOMAIN=xn--u4h.net +DAYS=365 +KEYTYPE=RSA +KEYSIZE=2048 +DIGEST=SHA256 +CRLHOURS=24 +CRLDAYS= + +# Cleanup temporary files at exit. +touch openssl.cnf +newcertdir=`mktemp -d` +cleanup() { + test -f openssl.cnf && rm -f openssl.cnf + test -f ca.txt && rm -f ca.txt + test -f ocsp.txt && rm -f ocsp.txt + test -n "$newcertdir" && rm -fr "$newcertdir" +} +trap cleanup EXIT + +# The first argument is either a common name value or a flag indicating that +# we're doing something other than issuing a cert. +commonname="$1" +refresh_crl=false +revoke_cert=false +ocsp_serve=false +if test "x$commonname" = "x-refresh-crl" ; then + refresh_crl=true + commonname="$1" +fi +if test "x$commonname" = "x-refresh_crl" ; then + refresh_crl=true + commonname="$1" +fi +if test "x$commonname" = "x-revoke" ; then + revoke_cert=true + shift + commonname="$1" +fi +if test "x$commonname" = "x-ocsp" ; then + ocsp_serve=true + commonname="$1" +fi +if test "x$commonname" = x ; then + echo Usage: `basename $0` 'commonname' user'[@domain]' '[more [...]]' + echo Usage: `basename $0` -revoke 'commonname' + echo Usage: `basename $0` -ocsp + echo Usage: `basename $0` -refresh-crl + echo More: + echo -e \\tKey usage: "[sign|signing|encrypt|encryption|all]" + echo -e \\tAuthority Access Info OCSP responder: "ocsp:URI" + echo -e \\tCRL distribution point: "crl:URI" + echo -e \\tSubject Alternative Name: + echo -e \\t\\tHostname: "*" + echo -e \\t\\tIP address: w.x.y.z + echo -e \\t\\tEmail address: "*@*.com/edu/net/org/local" + echo -e \\t\\tKerberos principal name: "*@*.COM/EDU/NET/ORG/LOCAL" + echo -e \\tExtended key usage: + echo -e \\t\\t1.... + echo -e \\t\\t2.... + echo -e \\t\\tid-kp-server-auth \| tls-server + echo -e \\t\\tid-kp-client-auth \| tls-client + echo -e \\t\\tid-kp-email-protection \| email + echo -e \\t\\tid-ms-kp-sc-logon \| id-ms-sc-logon + echo -e \\t\\tid-pkinit-kp-client-auth \| id-pkinit-client + echo -e \\t\\tid-pkinit-kp-kdc \| id-pkinit-kdc + echo -e \\t\\tca \| CA + exit 1 +fi + +# Choose a user name part for email attributes. +GIVENUSER=$2 +test x"$GIVENUSER" = x && GIVENUSER=$USER +echo "$GIVENUSER" | grep -q @ || GIVENUSER="$GIVENUSER"@$DOMAIN +DOMAIN=`echo "$GIVENUSER" | cut -f2- -d@` + +shift || true +shift || true + +# Done already? +done=: + +keygen() { + case "$KEYTYPE" in + DSA) + openssl dsaparam -out "$1".param $KEYSIZE + openssl gendsa "$1".param + ;; + RSA|*) + #openssl genrsa $KEYSIZE -passout pass:qweqwe + openssl genrsa $KEYSIZE + #openssl genrsa $KEYSIZE -nodes + ;; + esac +} + +# Set some defaults. +CA=FALSE +if test -s ca.crldp.uri.txt ; then + crlval="`cat ca.crldp.uri.txt`" + crl="URI:$crlval" +fi +if test -s ca.ocsp.uri.txt ; then + aiaval="`cat ca.ocsp.uri.txt`" + aia="OCSP;URI:$aiaval" +fi +if test -s ca.domain.txt ; then + domval="`cat ca.domain.txt`" + if test -n "$domval" ; then + DOMAIN="$domval" + fi +fi + +# Parse the arguments which indicate what sort of information we want. +while test $# -gt 0 ; do + type= + value="$1" + case "$value" in + RSA|rsa) + KEYTYPE=RSA + ;; + DSA|dsa) + KEYTYPE=DSA + ;; + OCSP:*|ocsp:*) + aiaval=`echo "$value" | cut -f2- -d:` + aia="OCSP;URI:$aiaval" + ;; + CRL:*|crl:*) + crlval=`echo "$value" | cut -f2- -d:` + crl="URI:$crlval" + ;; + signing|sign) + keyusage="${keyusage:+${keyusage},}nonRepudiation,digitalSignature" + ;; + encryption|encrypt) + keyusage="${keyusage:+${keyusage},}keyEncipherment,dataEncipherment" + ;; + all) + keyusage="digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign,encipherOnly,decipherOnly" + ;; + ca|CA) + CA=TRUE + keyusage="${keyusage:+${keyusage},}nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign" + ;; + 1.*|2.*|id-*|tls-*|email|mail|codesign) + ekuval=`echo "$value" | tr '[A-Z]' '[a-z]' | sed 's,\-,,g'` + case "$ekuval" in + idkpserverauth|tlsserver) ekuval=1.3.6.1.5.5.7.3.1;; + idkpclientauth|tlsclient) ekuval=1.3.6.1.5.5.7.3.2;; + idkpemailprotection|email|mail) ekuval=1.3.6.1.5.5.7.3.4;; + idkpcodesign|codesign) ekuval=1.3.6.1.5.5.7.3.3;; + idmskpsclogon|idmssclogon) ekuval=1.3.6.1.4.1.311.20.2.2;; + idpkinitkpclientauth|idpkinitclient) ekuval=1.3.6.1.5.2.3.4;; + idpkinitkpkdc|idpkinitkdc) ekuval=1.3.6.1.5.2.3.5;; + esac + if test -z "$eku" ; then + eku="$ekuval" + else + eku="$eku,$ekuval" + fi + ;; + *@*.COM|*@*.EDU|*@*.NET|*@*.ORG|*@*.LOCAL) + luser=`echo "$value" | tr '[A-Z]' '[a-z]'` + if test "$luser" = "$value" ; then + luser= + fi + type="otherName:1.3.6.1.5.2.2;SEQUENCE:$value,${luser:+otherName:1.3.6.1.4.1.311.20.2.3;UTF8:${luser},}otherName:1.3.6.1.4.1.311.20.2.3;UTF8" + unset luser + principals="$principals $value" + ;; + *@*.com|*@*.edu|*@*.net|*@*.org|*@*.local) type=email;; + [0-9]*.[0-9]*.[0-9]*.[0-9]*) type=IP;; + *) type=DNS;; + esac + if test -n "$type" ; then + newvalue="${type}:$value" + if test -z "$altnames" ; then + altnames="${newvalue}" + else + altnames="${altnames},${newvalue}" + fi + fi + shift +done + +# Build the configuration file, including bits on how to construct the CA +# certificate, an OCSP responder certificate, and the issued certificate. +cat > openssl.cnf <<- EOF +[ca] +default_ca = issuer + +[issuer] +private_key = `pwd`/ca.key +certificate = `pwd`/ca.crt +database = `pwd`/ca.db +serial = `pwd`/ca.srl +default_md = $DIGEST +new_certs_dir = $newcertdir +policy = no_policy + +[no_policy] + +[req_oids] +domainComponent = 0.9.2342.19200300.100.1.25 + +[req_ca] +prompt = no +oid_section = req_oids +distinguished_name = req_ca_name +default_md = $DIGEST +subjectKeyIdentifier=hash + +[req_ca_name] +C=US +#stateOrProvinceName=SomeState +localityName=SomeCity +O=SomeOrg +EOF +#echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf +cat >> openssl.cnf <<- EOF +#commonName = Test Certifying CA + +[v3_ca] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always +#authorityKeyIdentifier=keyid:always,issuer:always +keyUsage=nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign +basicConstraints=critical,CA:TRUE +nsComment="Testing CA Certificate" +EOF +if test -n "$aia" ; then + echo "authorityInfoAccess = ${aia}" >> openssl.cnf + echo -n "$aiaval" > ca.ocsp.uri.txt +fi +if test -n "$crl" ; then + echo "crlDistributionPoints = ${crl}" >> openssl.cnf + echo -n "$crlval" > ca.crldp.uri.txt +fi +echo "$DOMAIN" > ca.domain.txt +cat >> openssl.cnf <<- EOF + +[req_ocsp] +prompt = no +oid_section = req_oids +distinguished_name = req_ocsp_name +default_md = $DIGEST + +[req_ocsp_name] +C=US +#stateOrProvinceName=SomeState +localityName=SomeOrg +O=SomeOrg +EOF +#echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf +cat >> openssl.cnf <<- EOF +#commonName = OCSP Signer for Test Certifying CA + +[v3_ocsp] +subjectKeyIdentifier=hash +#authorityKeyIdentifier=keyid:always,issuer:always +authorityKeyIdentifier=keyid:always +keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement,keyCertSign,cRLSign +extendedKeyUsage=1.3.6.1.5.5.7.3.9 +#basicConstraints=CA:FALSE +basicConstraints=CA:TRUE +nsComment="Testing OCSP Certificate" +1.3.6.1.5.5.7.48.1.5=ASN1:NULL +EOF +if test -n "$aia" ; then + echo "authorityInfoAccess = ${aia}" >> openssl.cnf +fi +if test -n "$crl" ; then + echo "crlDistributionPoints = ${crl}" >> openssl.cnf +fi +cat >> openssl.cnf <<- EOF + +[req_issued] +prompt = no +oid_section = req_oids +distinguished_name = req_issued_name +default_md = $DIGEST + +[req_issued_name] +C=US +#stateOrProvinceName=SomeState +localityName=SomeCity +O=SomeOrg +EOF +#echo $DOMAIN | awk 'BEGIN {FS="."}{for(i=NF;i>0;i--){print NF-i ".domainComponent="$i;}}' >> openssl.cnf +#mail = $GIVENUSER +cat >> openssl.cnf <<- EOF +commonName = $commonname + +[v3_issued] +#certificatePolicies=2.5.29.32.0${eku:+,${eku}} +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always +#authorityKeyIdentifier=keyid:always,issuer:always +EOF +if test -n "$aia" ; then + echo "authorityInfoAccess = ${aia}" >> openssl.cnf +fi +if test -n "$crl" ; then + echo "crlDistributionPoints = ${crl}" >> openssl.cnf +fi +if test -n "$keyusage" ; then + echo "keyUsage = critical,${keyusage}" >> openssl.cnf +fi +if test -n "$altnames" ; then + echo "subjectAltName = ${altnames}" >> openssl.cnf +fi +if test -n "$eku" ; then + echo "extendedKeyUsage = ${eku}" >> openssl.cnf + : +fi +if test "x$CA" = xTRUE ; then + echo "basicConstraints=critical,CA:TRUE" >> openssl.cnf + echo 'nsComment="Testing CA Certificate for '"$commonname"'"' >> openssl.cnf +else + echo "basicConstraints=CA:FALSE" >> openssl.cnf + echo 'nsComment="Testing Certificate for '"$commonname"'"' >> openssl.cnf +fi +for value in $principals; do + user=`echo "$value" | cut -f1 -d@` + realm=`echo "$value" | cut -f2- -d@` + echo "" >> openssl.cnf + echo "[$value]" >> openssl.cnf + echo "realm=EXPLICIT:0,GeneralString:$realm" >> openssl.cnf + echo "kerberosname=EXPLICIT:1,SEQUENCE:krb5$user" >> openssl.cnf + + echo "" >> openssl.cnf + echo "[krb5$user]" >> openssl.cnf + echo "nametype=EXPLICIT:0,INTEGER:1" >> openssl.cnf + echo "namelist=EXPLICIT:1,SEQUENCE:krb5basic$user" >> openssl.cnf + + echo "[krb5basic$user]" >> openssl.cnf + count=0 + for part in `echo "$user" | sed 's,/, ,g'` ; do + echo "$count.part=GeneralString:$part" >> openssl.cnf + count=`expr "$count" + 1` + done +done + +# Create the data files for a new CA. +if ! test -s ca.srl ; then + (dd if=/dev/urandom bs=8 count=1 2> /dev/null) | od -t x1c | head -n 1 | awk '{$1="00";OFS="";print}' > ca.srl +else + echo "You already have a ca.srl file; not replacing." +fi +if ! test -s ca.db ; then + touch ca.db +else + echo "You already have a ca.db file; not replacing." +fi +if ! test -s ca.db.attr ; then + touch ca.db.attr +else + echo "You already have a ca.db.attr file; not replacing." +fi + +# If we need a CA key, generate one. +if ! test -s ca.key ; then + umask=`umask -p` + umask 077 + keygen ca > ca.key 2> /dev/null + $umask +else + echo "You already have a ca.key file; not replacing." + done=echo +fi + +# If we need a CA certificate, generate one. +if ! test -s ca.crt ; then + sed -i -e 's,^\[req_ca\]$,\[req\],g' `pwd`/openssl.cnf + openssl req -config `pwd`/openssl.cnf -new -key ca.key > ca.csr 2> /dev/null -passin pass:shim + sed -i -e 's,^\[req\]$,\[req_ca\],g' `pwd`/openssl.cnf + openssl x509 -extfile `pwd`/openssl.cnf -CAserial ca.srl -signkey ca.key -extensions v3_ca -req -in ca.csr -days $DAYS -out ca.crt ; : 2> /dev/null + openssl x509 -noout -text -in ca.crt > ca.txt + cat ca.crt >> ca.txt + cat ca.txt > ca.crt + rm ca.txt + cat ca.crt > ca.chain.crt +else + echo "You already have a ca.crt file; not replacing." + done=echo +fi + +# If we need an OCSP key, generate one. +if ! test -s ocsp.key ; then + umask=`umask -p` + umask 077 + keygen ocsp > ocsp.key 2> /dev/null + $umask +else + echo "You already have an ocsp.key file; not replacing." + done=echo +fi + +# Generate the OCSP signing cert. Set the X.509v3 basic constraints and EKU. +if ! test -s ocsp.crt ; then + sed -i -e 's,^\[req_ocsp\]$,\[req\],g' `pwd`/openssl.cnf + openssl req -config `pwd`/openssl.cnf -new -key ocsp.key > ocsp.csr 2> /dev/null + sed -i -e 's,^\[req\]$,\[req_ocsp\],g' `pwd`/openssl.cnf + openssl ca -batch -config `pwd`/openssl.cnf -extensions v3_ocsp -preserveDN -in ocsp.csr -days $DAYS -out ocsp.crt 2> /dev/null + openssl x509 -noout -text -in ocsp.crt > ocsp.txt + cat ocsp.crt >> ocsp.txt + cat ocsp.txt > ocsp.crt + rm ocsp.txt +else + echo "You already have an ocsp.crt file; not replacing." + done=echo +fi + +# If we were told to revoke the certificate with the specified common name, +# do so. +if $revoke_cert ; then + openssl ca -config `pwd`/openssl.cnf -revoke "$commonname".crt +fi + +# Always refresh the CRL. +openssl ca -config `pwd`/openssl.cnf -gencrl ${CRLHOURS:+-crlhours ${CRLHOURS}} ${CRLDAYS:+-crldays ${CRLDAYS}} -out ca.crl.pem +openssl crl -in ca.crl.pem -outform der -out ca.crl +openssl crl -in ca.crl -inform der -noout -text > ca.crl.pem +openssl crl -in ca.crl -inform der >> ca.crl.pem + +# If we were told to start up the mini OCSP server, do so. +if $ocsp_serve ; then + openssl ocsp -text -index `pwd`/ca.db -CA `pwd`/ca.crt -rsigner `pwd`/ocsp.crt -rkey `pwd`/ocsp.key -rother `pwd`/ocsp.crt -port "`cut -f3 -d/ ca.ocsp.uri.txt | sed -r 's,(^[^:]*),0.0.0.0,g'`" + exit 0 +fi + +# If we're just here to do a revocation or refresh the CRL, we're done. +if $revoke_cert || $refresh_crl ; then + exit 0 +fi + +# Create a new serial number and whatnot if this is a new sub-CA. +if test "x$CA" = xTRUE ; then + if ! test -d "$commonname" ; then + mkdir "$commonname" + fi + if ! test -s "$commonname/ca.srl" ; then + (dd if=/dev/urandom bs=8 count=1 2> /dev/null) | od -t x1c | head -n 1 | awk '{$1="00";OFS="";print}' > "$commonname/ca.srl" + else + echo "You already have a $commonname/ca.srl file; not replacing." + fi + if test -n "$aia" ; then + echo -n "$aiaval" > "$commonname/ca.ocsp.uri.txt" + fi + if test -n "$crl" ; then + echo -n "$crlval" > "$commonname/ca.crldp.uri.txt" + fi + echo "$DOMAIN" > "$commonname/ca.domain.txt" + touch "$commonname/ca.db" "$commonname/ca.db.attr" + cert="$commonname/ca.crt" + csr="$commonname/ca.csr" + key="$commonname/ca.key" + pem="$commonname/ca.pem" + pfx="$commonname/ca.p12" + ln -s ../`basename $0` "$commonname"/ +else + cert="$commonname.crt" + csr="$commonname.csr" + key="$commonname.key" + pem="$commonname.pem" + pfx="$commonname.p12" +fi + +# Generate the subject's certificate. Set the X.509v3 basic constraints. +if ! test -s "$cert" ; then + # Generate another key, unless we have a key or CSR. + if ! test -s "$key" && ! test -s "$csr" ; then + umask=`umask -p` + umask 077 + keygen "$commonname" > "$key" 2> /dev/null + $umask + else + echo "You already have a $key or $csr file; not replacing." + done=echo + fi + + if ! test -s "$csr" ; then + sed -i -e 's,^\[req_issued\]$,\[req\],g' `pwd`/openssl.cnf + openssl req -config `pwd`/openssl.cnf -new -key "$key" > "$csr" 2> /dev/null + sed -i -e 's,^\[req\]$,\[req_issued\],g' `pwd`/openssl.cnf + fi + openssl ca -batch -config `pwd`/openssl.cnf -extensions v3_issued -preserveDN -in "$csr" -days $DAYS -out "$cert" 2> /dev/null + openssl x509 -noout -text -in "$cert" > "$cert.txt" + cat "$cert" >> "$cert.txt" + cat "$cert.txt" > "$cert" + rm -f "$cert.txt" +else + echo "You already have a $cert file; not replacing." + done=echo +fi + +if test -s ca.chain.crt ; then + chain=ca.chain.crt +else + chain=ca.crt +fi +if test "x$CA" = xTRUE ; then + cat "$chain" "$cert" > "$commonname/ca.chain.crt" +fi + +# Create ca.pem and the subject's name.pem for the benefit of applications +# which expect both the private key and the certificate in one file. +umask=`umask -p` +umask 077 +if ! test -s ca.pem ; then + cat ca.key ca.crt > ca.pem +else + echo "You already have a ca.pem file; not replacing." + done=echo +fi +if ! test -s "$pem" ; then + cat "$key" "$cert" > "$pem" +else + echo "You already have a $pem file; not replacing." + done=echo +fi +if ! test -s "$pfx" ; then + #openssl pkcs12 -export -inkey "$key" -in "$cert" -name "$commonname" -out "$pfx" -nodes -passout pass:qweqwe + openssl pkcs12 -export -inkey "$key" -in "$cert" -name "$commonname" -out "$pfx" -nodes -passout pass: +else + echo "You already have a $pfx file; not replacing." + done=echo +fi +$umask +$done + +echo CA certificate: +openssl x509 -noout -issuer -in ca.crt | sed s,=\ ,\ ,g +openssl x509 -noout -subject -in ca.crt | sed s,=\ ,\ ,g +echo +echo End entity certificate: +openssl x509 -noout -issuer -in "$cert" | sed s,=\ ,\ ,g +openssl x509 -noout -subject -in "$cert" | sed s,=\ ,\ ,g +openssl x509 -noout -serial -in "$cert" | sed s,=,\ ,g +echo +echo PKCS12 bag: +openssl pkcs12 -in "$pfx" -nodes -nokeys -nocerts -info -passin pass: +#openssl pkcs12 -in "$pfx" -nodes -nokeys -nocerts -info -passin pass:qweqwe +echo +echo Verifying: +echo + openssl verify -CAfile "$chain" "$cert" +openssl verify -CAfile "$chain" "$cert" diff --git a/netboot.c b/netboot.c new file mode 100644 index 00000000..90fb9cba --- /dev/null +++ b/netboot.c @@ -0,0 +1,370 @@ +/* + * netboot - trivial UEFI first-stage bootloader netboot support + * + * Copyright 2012 Red Hat, Inc <mjg@redhat.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Significant portions of this code are derived from Tianocore + * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel + * Corporation. + */ + +#include <efi.h> +#include <efilib.h> +#include <string.h> +#include "shim.h" +#include "netboot.h" + + +static inline unsigned short int __swap16(unsigned short int x) +{ + __asm__("xchgb %b0,%h0" + : "=q" (x) + : "0" (x)); + return x; +} + +#define ntohs(x) __swap16(x) +#define htons(x) ntohs(x) + +static EFI_PXE_BASE_CODE *pxe; +static EFI_IP_ADDRESS tftp_addr; +static char *full_path; + + +/* + * Not in the EFI header set yet, so I have to declare it here + */ +typedef struct { + UINT32 MessageType:8; + UINT32 TransactionId:24; + UINT8 DhcpOptions[1024]; +} EFI_PXE_BASE_CODE_DHCPV6_PACKET; + +typedef struct { + UINT16 OpCode; + UINT16 Length; + UINT8 Data[1]; +} EFI_DHCP6_PACKET_OPTION; + +/* + * usingNetboot + * Returns TRUE if we identify a protocol that is enabled and Providing us with + * the needed information to fetch a grubx64.efi image + */ +BOOLEAN findNetboot(EFI_HANDLE image_handle) +{ + UINTN bs = sizeof(EFI_HANDLE); + EFI_GUID pxe_base_code_protocol = EFI_PXE_BASE_CODE_PROTOCOL; + EFI_HANDLE *hbuf; + BOOLEAN rc = FALSE; + void *buffer = AllocatePool(bs); + UINTN errcnt = 0; + UINTN i; + EFI_STATUS status; + + if (!buffer) + return FALSE; + +try_again: + status = uefi_call_wrapper(BS->LocateHandle,5, ByProtocol, + &pxe_base_code_protocol, NULL, &bs, + buffer); + + if (status == EFI_BUFFER_TOO_SMALL) { + errcnt++; + FreePool(buffer); + if (errcnt > 1) + return FALSE; + buffer = AllocatePool(bs); + if (!buffer) + return FALSE; + goto try_again; + } + + if (status == EFI_NOT_FOUND) { + FreePool(buffer); + return FALSE; + } + + /* + * We have a list of pxe supporting protocols, lets see if any are + * active + */ + hbuf = buffer; + pxe = NULL; + for (i=0; i < (bs / sizeof(EFI_HANDLE)); i++) { + status = uefi_call_wrapper(BS->OpenProtocol, 6, hbuf[i], + &pxe_base_code_protocol, + &pxe, image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (status != EFI_SUCCESS) { + pxe = NULL; + continue; + } + + if (!pxe || !pxe->Mode) { + pxe = NULL; + continue; + } + + if (pxe->Mode->Started && pxe->Mode->DhcpAckReceived) { + /* + * We've located a pxe protocol handle thats been + * started and has received an ACK, meaning its + * something we'll be able to get tftp server info + * out of + */ + rc = TRUE; + break; + } + + } + + FreePool(buffer); + return rc; +} + +static char *get_v6_bootfile_url(EFI_PXE_BASE_CODE_DHCPV6_PACKET *pkt) +{ + void *optr; + EFI_DHCP6_PACKET_OPTION *option; + char *url; + UINT32 urllen; + + optr = pkt->DhcpOptions; + + for(;;) { + option = (EFI_DHCP6_PACKET_OPTION *)optr; + + if (ntohs(option->OpCode) == 0) + return NULL; + + if (ntohs(option->OpCode) == 59) { + /* This is the bootfile url option */ + urllen = ntohs(option->Length); + url = AllocatePool(urllen+2); + if (!url) + return NULL; + memset(url, 0, urllen+2); + memcpy(url, option->Data, urllen); + return url; + } + optr += 4 + ntohs(option->Length); + } + + return NULL; +} + +static UINT16 str2ns(UINT8 *str) +{ + UINT16 ret = 0; + UINT8 v; + for(;*str;str++) { + if ('0' <= *str && *str <= '9') + v = *str - '0'; + else if ('A' <= *str && *str <= 'F') + v = *str - 'A' + 10; + else if ('a' <= *str && *str <= 'f') + v = *str - 'a' + 10; + else + v = 0; + ret = (ret << 4) + v; + } + return htons(ret); +} + +static UINT8 *str2ip6(char *str) +{ + UINT8 i, j, p; + size_t len; + UINT8 *a, *b, t; + static UINT16 ip[8]; + + for(i=0; i < 8; i++) { + ip[i] = 0; + } + len = strlen((UINT8 *)str); + a = b = (UINT8 *)str; + for(i=p=0; i < len; i++, b++) { + if (*b != ':') + continue; + *b = '\0'; + ip[p++] = str2ns(a); + *b = ':'; + a = b + 1; + if ( *(b+1) == ':' ) + break; + } + a = b = (UINT8 *)(str + len); + for(j=len, p=7; j > i; j--, a--) { + if (*a != ':') + continue; + t = *b; + *b = '\0'; + ip[p--] = str2ns(a+1); + *b = t; + b = a; + } + return (UINT8 *)ip; +} + +static BOOLEAN extract_tftp_info(char *url) +{ + char *start, *end; + char ip6str[128]; + char *template = "/grubx64.efi"; + + if (strncmp((UINT8 *)url, (UINT8 *)"tftp://", 7)) { + Print(L"URLS MUST START WITH tftp://\n"); + return FALSE; + } + start = url + 7; + if (*start != '[') { + Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n"); + return FALSE; + } + + start++; + end = start; + while ((*end != '\0') && (*end != ']')) { + end++; + } + if (end == '\0') { + Print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n"); + return FALSE; + } + *end = '\0'; + memset(ip6str, 0, 128); + memcpy(ip6str, start, strlen((UINT8 *)start)); + *end = ']'; + end++; + memcpy(&tftp_addr.v6, str2ip6(ip6str), 16); + full_path = AllocatePool(strlen((UINT8 *)end)+strlen((UINT8 *)template)+1); + if (!full_path) + return FALSE; + memset(full_path, 0, strlen((UINT8 *)end)+strlen((UINT8 *)template)); + memcpy(full_path, end, strlen((UINT8 *)end)); + end = strrchr(full_path, '/'); + if (!end) + end = full_path; + memcpy(end, template, strlen((UINT8 *)template)); + + return TRUE; +} + +static EFI_STATUS parseDhcp6() +{ + EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw; + char *bootfile_url; + + + bootfile_url = get_v6_bootfile_url(packet); + if (extract_tftp_info(bootfile_url) == FALSE) + return EFI_NOT_FOUND; + if (!bootfile_url) + return EFI_NOT_FOUND; + return EFI_SUCCESS; +} + +static EFI_STATUS parseDhcp4() +{ + char *template = "/grubx64.efi"; + char *tmp = AllocatePool(16); + + + if (!tmp) + return EFI_OUT_OF_RESOURCES; + + + memcpy(&tftp_addr.v4, pxe->Mode->DhcpAck.Dhcpv4.BootpSiAddr, 4); + + memcpy(tmp, template, 12); + tmp[13] = '\0'; + full_path = tmp; + + /* Note we don't capture the filename option here because we know its shim.efi + * We instead assume the filename at the end of the path is going to be grubx64.efi + */ + return EFI_SUCCESS; +} + +EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle) +{ + + EFI_STATUS rc; + + if (!pxe) + return EFI_NOT_READY; + + memset((UINT8 *)&tftp_addr, 0, sizeof(tftp_addr)); + + /* + * If we've discovered an active pxe protocol figure out + * if its ipv4 or ipv6 + */ + if (pxe->Mode->UsingIpv6){ + rc = parseDhcp6(); + } else + rc = parseDhcp4(); + return rc; +} + +EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz) +{ + EFI_STATUS rc; + EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE; + BOOLEAN overwrite = FALSE; + BOOLEAN nobuffer = FALSE; + UINTN blksz = 512; + + Print(L"Fetching Netboot Image\n"); + if (*buffer == NULL) { + *buffer = AllocatePool(4096 * 1024); + if (!*buffer) + return EFI_OUT_OF_RESOURCES; + *bufsiz = 4096 * 1024; + } + +try_again: + rc = uefi_call_wrapper(pxe->Mtftp, 10, pxe, read, *buffer, overwrite, + &bufsiz, &blksz, &tftp_addr, full_path, NULL, nobuffer); + + if (rc == EFI_BUFFER_TOO_SMALL) { + /* try again, doubling buf size */ + *bufsiz *= 2; + FreePool(*buffer); + *buffer = AllocatePool(*bufsiz); + if (!*buffer) + return EFI_OUT_OF_RESOURCES; + goto try_again; + } + + return rc; + +} diff --git a/netboot.h b/netboot.h new file mode 100644 index 00000000..2cdb4218 --- /dev/null +++ b/netboot.h @@ -0,0 +1,9 @@ +#ifndef _NETBOOT_H_ +#define _NETBOOT_H_ + +extern BOOLEAN findNetboot(EFI_HANDLE image_handle); + +extern EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle); + +extern EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINTN *bufsiz); +#endif @@ -39,8 +39,11 @@ #include "PeImage.h" #include "shim.h" #include "signature.h" +#include "netboot.h" +#include "shim_cert.h" #define SECOND_STAGE L"\\grub.efi" +#define MOK_MANAGER L"\\MokManager.efi" static EFI_SYSTEM_TABLE *systab; static EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table); @@ -50,31 +53,38 @@ static EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TAB */ extern UINT8 vendor_cert[]; extern UINT32 vendor_cert_size; +extern EFI_SIGNATURE_LIST *vendor_dbx; +extern UINT32 vendor_dbx_size; #define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }} +static UINT8 insecure_mode; + typedef enum { DATA_FOUND, DATA_NOT_FOUND, VAR_NOT_FOUND } CHECK_STATUS; -static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, +typedef struct { + UINT32 MokSize; + UINT8 *Mok; +} MokListNode; + +static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, UINT32 *attributes, UINTN *size, void **buffer) { EFI_STATUS efi_status; - UINT32 attributes; char allocate = !(*size); efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, - &attributes, size, buffer); + attributes, size, buffer); if (efi_status != EFI_BUFFER_TOO_SMALL || !allocate) { return efi_status; } - if (allocate) - *buffer = AllocatePool(*size); + *buffer = AllocatePool(*size); if (!*buffer) { Print(L"Unable to allocate variable buffer\n"); @@ -82,7 +92,7 @@ static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, } efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, - &attributes, size, *buffer); + attributes, size, *buffer); return efi_status; } @@ -202,25 +212,16 @@ static EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, return EFI_SUCCESS; } -static CHECK_STATUS check_db_cert(CHAR16 *dbname, WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash) +static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList, + UINTN dbsize, + WIN_CERTIFICATE_EFI_PKCS *data, + UINT8 *hash) { - EFI_STATUS efi_status; - EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; - EFI_SIGNATURE_LIST *CertList; EFI_SIGNATURE_DATA *Cert; - UINTN dbsize = 0; UINTN CertCount, Index; BOOLEAN IsFound = FALSE; - void *db; EFI_GUID CertType = EfiCertX509Guid; - efi_status = get_variable(dbname, secure_var, &dbsize, &db); - - if (efi_status != EFI_SUCCESS) - return VAR_NOT_FOUND; - - CertList = db; - while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { if (CompareGuid (&CertList->SignatureType, &CertType) == 0) { CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; @@ -233,44 +234,60 @@ static CHECK_STATUS check_db_cert(CHAR16 *dbname, WIN_CERTIFICATE_EFI_PKCS *data hash, SHA256_DIGEST_SIZE); if (IsFound) break; + + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); } - Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); } + if (IsFound) + break; + dbsize -= CertList->SignatureListSize; CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); } - FreePool(db); - if (IsFound) return DATA_FOUND; return DATA_NOT_FOUND; } -static CHECK_STATUS check_db_hash(CHAR16 *dbname, UINT8 *data) +static CHECK_STATUS check_db_cert(CHAR16 *dbname, EFI_GUID guid, + WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash) { + CHECK_STATUS rc; EFI_STATUS efi_status; - EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; EFI_SIGNATURE_LIST *CertList; - EFI_SIGNATURE_DATA *Cert; UINTN dbsize = 0; - UINTN CertCount, Index; - BOOLEAN IsFound = FALSE; + UINT32 attributes; void *db; - unsigned int SignatureSize = SHA256_DIGEST_SIZE; - EFI_GUID CertType = EfiHashSha256Guid; - efi_status = get_variable(dbname, secure_var, &dbsize, &db); + efi_status = get_variable(dbname, guid, &attributes, &dbsize, &db); - if (efi_status != EFI_SUCCESS) { + if (efi_status != EFI_SUCCESS) return VAR_NOT_FOUND; - } CertList = db; + rc = check_db_cert_in_ram(CertList, dbsize, data, hash); + + FreePool(db); + + return rc; +} + +/* + * Check a hash against an EFI_SIGNATURE_LIST in a buffer + */ +static CHECK_STATUS check_db_hash_in_ram(EFI_SIGNATURE_LIST *CertList, + UINTN dbsize, UINT8 *data, + int SignatureSize, EFI_GUID CertType) +{ + EFI_SIGNATURE_DATA *Cert; + UINTN CertCount, Index; + BOOLEAN IsFound = FALSE; + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); @@ -295,29 +312,93 @@ static CHECK_STATUS check_db_hash(CHAR16 *dbname, UINT8 *data) CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); } - FreePool(db); - if (IsFound) return DATA_FOUND; return DATA_NOT_FOUND; } -static EFI_STATUS check_blacklist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *hash) +/* + * Check a hash against an EFI_SIGNATURE_LIST in a UEFI variable + */ +static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data, + int SignatureSize, EFI_GUID CertType) +{ + EFI_STATUS efi_status; + EFI_SIGNATURE_LIST *CertList; + UINT32 attributes; + UINTN dbsize = 0; + void *db; + + efi_status = get_variable(dbname, guid, &attributes, &dbsize, &db); + + if (efi_status != EFI_SUCCESS) { + return VAR_NOT_FOUND; + } + + CertList = db; + + CHECK_STATUS rc = check_db_hash_in_ram(CertList, dbsize, data, + SignatureSize, CertType); + FreePool(db); + return rc; + +} + +/* + * Check whether the binary signature or hash are present in dbx or the + * built-in blacklist + */ +static EFI_STATUS check_blacklist (WIN_CERTIFICATE_EFI_PKCS *cert, + UINT8 *sha256hash, UINT8 *sha1hash) { - if (check_db_hash(L"dbx", hash) == DATA_FOUND) + EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; + + if (check_db_hash_in_ram(vendor_dbx, vendor_dbx_size, sha256hash, + SHA256_DIGEST_SIZE, EfiHashSha256Guid) == + DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_hash_in_ram(vendor_dbx, vendor_dbx_size, sha1hash, + SHA1_DIGEST_SIZE, EfiHashSha1Guid) == + DATA_FOUND) return EFI_ACCESS_DENIED; - if (check_db_cert(L"dbx", cert, hash) == DATA_FOUND) + if (check_db_cert_in_ram(vendor_dbx, vendor_dbx_size, cert, + sha256hash) == DATA_FOUND) + return EFI_ACCESS_DENIED; + + if (check_db_hash(L"dbx", secure_var, sha256hash, SHA256_DIGEST_SIZE, + EfiHashSha256Guid) == DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_hash(L"dbx", secure_var, sha1hash, SHA1_DIGEST_SIZE, + EfiHashSha1Guid) == DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_cert(L"dbx", secure_var, cert, sha256hash) == DATA_FOUND) return EFI_ACCESS_DENIED; return EFI_SUCCESS; } -static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *hash) +/* + * Check whether the binary signature or hash are present in db or MokList + */ +static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, + UINT8 *sha256hash, UINT8 *sha1hash) { - if (check_db_hash(L"db", hash) == DATA_FOUND) + EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; + EFI_GUID shim_var = SHIM_LOCK_GUID; + + if (check_db_hash(L"db", secure_var, sha256hash, SHA256_DIGEST_SIZE, + EfiHashSha256Guid) == DATA_FOUND) + return EFI_SUCCESS; + if (check_db_hash(L"db", secure_var, sha1hash, SHA1_DIGEST_SIZE, + EfiHashSha1Guid) == DATA_FOUND) return EFI_SUCCESS; - if (check_db_cert(L"db", cert, hash) == DATA_FOUND) + if (check_db_hash(L"MokList", shim_var, sha256hash, SHA256_DIGEST_SIZE, + EfiHashSha256Guid) == DATA_FOUND) + return EFI_SUCCESS; + if (check_db_cert(L"db", secure_var, cert, sha256hash) == DATA_FOUND) + return EFI_SUCCESS; + if (check_db_cert(L"MokList", shim_var, cert, sha256hash) == DATA_FOUND) return EFI_SUCCESS; return EFI_ACCESS_DENIED; @@ -333,8 +414,13 @@ static BOOLEAN secure_mode (void) EFI_GUID global_var = EFI_GLOBAL_VARIABLE; UINTN charsize = sizeof(char); UINT8 sb, setupmode; + UINT32 attributes; + + if (insecure_mode) + return FALSE; - status = get_variable(L"SecureBoot", global_var, &charsize, (void *)&sb); + status = get_variable(L"SecureBoot", global_var, &attributes, &charsize, + (void *)&sb); /* FIXME - more paranoia here? */ if (status != EFI_SUCCESS || sb != 1) { @@ -342,7 +428,8 @@ static BOOLEAN secure_mode (void) return FALSE; } - status = get_variable(L"SetupMode", global_var, &charsize, (void *)&setupmode); + status = get_variable(L"SetupMode", global_var, &attributes, &charsize, + (void *)&setupmode); if (status == EFI_SUCCESS && setupmode == 1) { Print(L"Platform is in setup mode\n"); @@ -353,49 +440,38 @@ static BOOLEAN secure_mode (void) } /* - * Check that the signature is valid and matches the binary + * Calculate the SHA1 and SHA256 hashes of a binary */ -static EFI_STATUS verify_buffer (char *data, int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context, int whitelist) + +static EFI_STATUS generate_hash (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash) + { + unsigned int sha256ctxsize, sha1ctxsize; unsigned int size = datasize; - unsigned int ctxsize; - void *ctx = NULL; - UINT8 hash[SHA256_DIGEST_SIZE]; - EFI_STATUS status = EFI_ACCESS_DENIED; + void *sha256ctx = NULL, *sha1ctx = NULL; char *hashbase; unsigned int hashsize; - WIN_CERTIFICATE_EFI_PKCS *cert; unsigned int SumOfBytesHashed, SumOfSectionBytes; unsigned int index, pos; EFI_IMAGE_SECTION_HEADER *Section; EFI_IMAGE_SECTION_HEADER *SectionHeader = NULL; EFI_IMAGE_SECTION_HEADER *SectionCache; + EFI_STATUS status = EFI_SUCCESS; - cert = ImageAddress (data, size, context->SecDir->VirtualAddress); - - if (!cert) { - Print(L"Certificate located outside the image\n"); - return EFI_INVALID_PARAMETER; - } - - if (cert->Hdr.wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { - Print(L"Unsupported certificate type %x\n", - cert->Hdr.wCertificateType); - return EFI_UNSUPPORTED; - } - - /* FIXME: Check which kind of hash */ + sha256ctxsize = Sha256GetContextSize(); + sha256ctx = AllocatePool(sha256ctxsize); - ctxsize = Sha256GetContextSize(); - ctx = AllocatePool(ctxsize); + sha1ctxsize = Sha1GetContextSize(); + sha1ctx = AllocatePool(sha1ctxsize); - if (!ctx) { + if (!sha256ctx || !sha1ctx) { Print(L"Unable to allocate memory for hash context\n"); return EFI_OUT_OF_RESOURCES; } - if (!Sha256Init(ctx)) { + if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx)) { Print(L"Unable to initialise hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; @@ -406,7 +482,8 @@ static EFI_STATUS verify_buffer (char *data, int datasize, hashsize = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum - hashbase; - if (!(Sha256Update(ctx, hashbase, hashsize))) { + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize))) { Print(L"Unable to generate hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; @@ -417,7 +494,8 @@ static EFI_STATUS verify_buffer (char *data, int datasize, sizeof (int); hashsize = (char *)context->SecDir - hashbase; - if (!(Sha256Update(ctx, hashbase, hashsize))) { + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize))) { Print(L"Unable to generate hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; @@ -428,7 +506,8 @@ static EFI_STATUS verify_buffer (char *data, int datasize, hashsize = context->PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders - (int) ((char *) (&context->PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY + 1]) - data); - if (!(Sha256Update(ctx, hashbase, hashsize))) { + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize))) { Print(L"Unable to generate hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; @@ -484,10 +563,12 @@ static EFI_STATUS verify_buffer (char *data, int datasize, if (!hashbase) { Print(L"Malformed section header\n"); - return EFI_INVALID_PARAMETER; + status = EFI_INVALID_PARAMETER; + goto done; } - if (!(Sha256Update(ctx, hashbase, hashsize))) { + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize))) { Print(L"Unable to generate hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; @@ -503,50 +584,145 @@ static EFI_STATUS verify_buffer (char *data, int datasize, context->PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size - SumOfBytesHashed); - if (!(Sha256Update(ctx, hashbase, hashsize))) { + if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || + !(Sha1Update(sha1ctx, hashbase, hashsize))) { Print(L"Unable to generate hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; } } - if (!(Sha256Final(ctx, hash))) { + if (!(Sha256Final(sha256ctx, sha256hash)) || + !(Sha1Final(sha1ctx, sha1hash))) { Print(L"Unable to finalise hash\n"); status = EFI_OUT_OF_RESOURCES; goto done; } - status = check_blacklist(cert, hash); +done: + if (SectionHeader) + FreePool(SectionHeader); + if (sha1ctx) + FreePool(sha1ctx); + if (sha256ctx) + FreePool(sha256ctx); + + return status; +} + +/* + * Ensure that the MOK database hasn't been set or modified from an OS + */ +static EFI_STATUS verify_mok (void) { + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS status = EFI_SUCCESS; + void *MokListData = NULL; + UINTN MokListDataSize = 0; + UINT32 attributes; + + status = get_variable(L"MokList", shim_lock_guid, &attributes, + &MokListDataSize, &MokListData); + + if (attributes & EFI_VARIABLE_RUNTIME_ACCESS) { + Print(L"MokList is compromised!\nErase all keys in MokList!\n"); + if (LibDeleteVariable(L"MokList", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to erase MokList\n"); + } + status = EFI_ACCESS_DENIED; + return status; + } + + return EFI_SUCCESS; +} + +/* + * Check that the signature is valid and matches the binary + */ +static EFI_STATUS verify_buffer (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + UINT8 sha256hash[SHA256_DIGEST_SIZE]; + UINT8 sha1hash[SHA1_DIGEST_SIZE]; + EFI_STATUS status = EFI_ACCESS_DENIED; + WIN_CERTIFICATE_EFI_PKCS *cert; + unsigned int size = datasize; + + if (context->SecDir->Size == 0) { + Print(L"Empty security header\n"); + return EFI_INVALID_PARAMETER; + } + + cert = ImageAddress (data, size, context->SecDir->VirtualAddress); + + if (!cert) { + Print(L"Certificate located outside the image\n"); + return EFI_INVALID_PARAMETER; + } + + if (cert->Hdr.wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + Print(L"Unsupported certificate type %x\n", + cert->Hdr.wCertificateType); + return EFI_UNSUPPORTED; + } + + status = generate_hash(data, datasize, context, sha256hash, sha1hash); + + if (status != EFI_SUCCESS) + return status; + + /* + * Check that the MOK database hasn't been modified + */ + verify_mok(); + + /* + * Ensure that the binary isn't blacklisted + */ + status = check_blacklist(cert, sha256hash, sha1hash); if (status != EFI_SUCCESS) { Print(L"Binary is blacklisted\n"); - goto done; + return status; } - if (whitelist) { - status = check_whitelist(cert, hash); + /* + * Check whether the binary is whitelisted in any of the firmware + * databases + */ + status = check_whitelist(cert, sha256hash, sha1hash); - if (status == EFI_SUCCESS) { - Print(L"Binary is whitelisted\n"); - goto done; - } + if (status == EFI_SUCCESS) { + Print(L"Binary is whitelisted\n"); + return status; } - if (!AuthenticodeVerify(cert->CertData, - context->SecDir->Size - sizeof(cert->Hdr), - vendor_cert, vendor_cert_size, hash, - SHA256_DIGEST_SIZE)) { - Print(L"Invalid signature\n"); - status = EFI_ACCESS_DENIED; - } else { + /* + * Check against the shim build key + */ + if (AuthenticodeVerify(cert->CertData, + context->SecDir->Size - sizeof(cert->Hdr), + shim_cert, sizeof(shim_cert), sha256hash, + SHA256_DIGEST_SIZE)) { status = EFI_SUCCESS; + Print(L"Binary is verified by the vendor certificate\n"); + return status; } -done: - if (SectionHeader) - FreePool(SectionHeader); - if (ctx) - FreePool(ctx); + + /* + * And finally, check against shim's built-in key + */ + if (AuthenticodeVerify(cert->CertData, + context->SecDir->Size - sizeof(cert->Hdr), + vendor_cert, vendor_cert_size, sha256hash, + SHA256_DIGEST_SIZE)) { + status = EFI_SUCCESS; + Print(L"Binary is verified by the vendor certificate\n"); + return status; + } + + Print(L"Invalid signature\n"); + status = EFI_ACCESS_DENIED; return status; } @@ -560,9 +736,19 @@ static EFI_STATUS read_header(void *data, unsigned int datasize, EFI_IMAGE_DOS_HEADER *DosHdr = data; EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; + if (datasize < sizeof(EFI_IMAGE_DOS_HEADER)) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); + if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { Print(L"Unsupported image type\n"); return EFI_UNSUPPORTED; @@ -589,24 +775,28 @@ static EFI_STATUS read_header(void *data, unsigned int datasize, context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - if (context->SecDir->VirtualAddress >= datasize) { - Print(L"Malformed security header\n"); - return EFI_INVALID_PARAMETER; + if (context->ImageSize < context->SizeOfHeaders) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; } - if (context->SecDir->Size == 0) { - Print(L"Empty security header\n"); - return EFI_INVALID_PARAMETER; + if (((UINT8 *)context->SecDir - (UINT8 *)data) > (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) { + Print(L"Invalid image\n"); + return EFI_UNSUPPORTED; } + if (context->SecDir->VirtualAddress >= datasize) { + Print(L"Malformed security header\n"); + return EFI_INVALID_PARAMETER; + } return EFI_SUCCESS; } /* * Once the image has been loaded it needs to be validated and relocated */ -static EFI_STATUS handle_grub (void *data, unsigned int datasize, - EFI_LOADED_IMAGE *li) +static EFI_STATUS handle_image (void *data, unsigned int datasize, + EFI_LOADED_IMAGE *li) { EFI_STATUS efi_status; char *buffer; @@ -615,14 +805,20 @@ static EFI_STATUS handle_grub (void *data, unsigned int datasize, char *base, *end; PE_COFF_LOADER_IMAGE_CONTEXT context; + /* + * The binary header contains relevant context and section pointers + */ efi_status = read_header(data, datasize, &context); if (efi_status != EFI_SUCCESS) { Print(L"Failed to read header\n"); return efi_status; } + /* + * We only need to verify the binary if we're in secure mode + */ if (secure_mode ()) { - efi_status = verify_buffer(data, datasize, &context, 0); + efi_status = verify_buffer(data, datasize, &context); if (efi_status != EFI_SUCCESS) { Print(L"Verification failed\n"); @@ -639,6 +835,9 @@ static EFI_STATUS handle_grub (void *data, unsigned int datasize, CopyMem(buffer, data, context.SizeOfHeaders); + /* + * Copy the executable's sections to their desired offsets + */ Section = context.FirstSection; for (i = 0; i < context.NumberOfSections; i++) { size = Section->Misc.VirtualSize; @@ -663,6 +862,9 @@ static EFI_STATUS handle_grub (void *data, unsigned int datasize, Section += 1; } + /* + * Run the relocation fixups + */ efi_status = relocate_coff(&context, buffer); if (efi_status != EFI_SUCCESS) { @@ -672,6 +874,10 @@ static EFI_STATUS handle_grub (void *data, unsigned int datasize, } entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint); + /* + * grub needs to know its location and size in memory, so fix up + * the loaded image protocol values + */ li->ImageBase = buffer; li->ImageSize = context.ImageSize; @@ -684,7 +890,12 @@ static EFI_STATUS handle_grub (void *data, unsigned int datasize, return EFI_SUCCESS; } -static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, EFI_DEVICE_PATH **grubpath, CHAR16 **PathName) +/* + * Generate the path of an executable given shim's path and the name + * of the executable + */ +static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, CHAR16 *ImagePath, + EFI_DEVICE_PATH **grubpath, CHAR16 **PathName) { EFI_DEVICE_PATH *devpath; EFI_HANDLE device; @@ -707,10 +918,10 @@ static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, EFI_DEVICE_PATH **grubpath bootpath[i+1] = '\0'; - if (bootpath[i-i] == '\\') + if (i == 0 || bootpath[i-i] == '\\') bootpath[i] = '\0'; - *PathName = AllocatePool(StrSize(bootpath) + StrSize(SECOND_STAGE)); + *PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath)); if (!*PathName) { Print(L"Failed to allocate path buffer\n"); @@ -720,7 +931,7 @@ static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, EFI_DEVICE_PATH **grubpath *PathName[0] = '\0'; StrCat(*PathName, bootpath); - StrCat(*PathName, SECOND_STAGE); + StrCat(*PathName, ImagePath); *grubpath = FileDevicePath(device, *PathName); @@ -729,10 +940,10 @@ error: } /* - * Locate the second stage bootloader and read it into a buffer + * Open the second stage bootloader and read it into a buffer */ -static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, - int *datasize, CHAR16 *PathName) +static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data, + int *datasize, CHAR16 *PathName) { EFI_GUID simple_file_system_protocol = SIMPLE_FILE_SYSTEM_PROTOCOL; EFI_GUID file_info_id = EFI_FILE_INFO_ID; @@ -745,8 +956,12 @@ static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, device = li->DeviceHandle; + /* + * Open the device + */ efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, device, - &simple_file_system_protocol, &drive); + &simple_file_system_protocol, + (void **)&drive); if (efi_status != EFI_SUCCESS) { Print(L"Failed to find fs\n"); @@ -760,6 +975,9 @@ static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, goto error; } + /* + * And then open the file + */ efi_status = uefi_call_wrapper(root->Open, 5, root, &grub, PathName, EFI_FILE_MODE_READ, 0); @@ -776,10 +994,15 @@ static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, goto error; } + /* + * Find out how big the file is in order to allocate the storage + * buffer + */ efi_status = uefi_call_wrapper(grub->GetInfo, 4, grub, &file_info_id, &buffersize, fileinfo); if (efi_status == EFI_BUFFER_TOO_SMALL) { + FreePool(fileinfo); fileinfo = AllocatePool(buffersize); if (!fileinfo) { Print(L"Unable to allocate file info buffer\n"); @@ -805,6 +1028,10 @@ static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, efi_status = EFI_OUT_OF_RESOURCES; goto error; } + + /* + * Perform the actual read + */ efi_status = uefi_call_wrapper(grub->Read, 3, grub, &buffersize, *data); @@ -822,19 +1049,24 @@ static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, *datasize = buffersize; + FreePool(fileinfo); + return EFI_SUCCESS; error: if (*data) { FreePool(*data); *data = NULL; } - if (PathName) - FreePool(PathName); + if (fileinfo) FreePool(fileinfo); return efi_status; } +/* + * Protocol entry point. If secure boot is enabled, verify that the provided + * buffer is signed with a trusted key. + */ EFI_STATUS shim_verify (void *buffer, UINT32 size) { EFI_STATUS status; @@ -848,75 +1080,238 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) if (status != EFI_SUCCESS) return status; - status = verify_buffer(buffer, size, &context, 1); + status = verify_buffer(buffer, size, &context); return status; } -EFI_STATUS init_grub(EFI_HANDLE image_handle) +/* + * Load and run an EFI executable + */ +EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) { + EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL; EFI_STATUS efi_status; - EFI_HANDLE grub_handle = NULL; EFI_LOADED_IMAGE *li, li_bak; - EFI_DEVICE_PATH *grubpath; - CHAR16 *PathName; - EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL; + EFI_DEVICE_PATH *path; + CHAR16 *PathName = NULL; + void *sourcebuffer = NULL; + UINTN sourcesize = 0; void *data = NULL; int datasize; + /* + * We need to refer to the loaded image protocol on the running + * binary in order to find our path + */ efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image_handle, - &loaded_image_protocol, &li); + &loaded_image_protocol, (void **)&li); if (efi_status != EFI_SUCCESS) { Print(L"Unable to init protocol\n"); return efi_status; } - efi_status = generate_path(li, &grubpath, &PathName); + /* + * Build a new path from the existing one plus the executable name + */ + efi_status = generate_path(li, ImagePath, &path, &PathName); if (efi_status != EFI_SUCCESS) { - Print(L"Unable to generate grub path\n"); + Print(L"Unable to generate path: %s\n", ImagePath); goto done; } - efi_status = uefi_call_wrapper(BS->LoadImage, 6, FALSE, image_handle, - grubpath, NULL, 0, &grub_handle); - + if (findNetboot(image_handle)) { + efi_status = parseNetbootinfo(image_handle); + if (efi_status != EFI_SUCCESS) { + Print(L"Netboot parsing failed: %d\n", efi_status); + return EFI_PROTOCOL_ERROR; + } + efi_status = FetchNetbootimage(image_handle, &sourcebuffer, + &sourcesize); + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to fetch TFTP image\n"); + return efi_status; + } + data = sourcebuffer; + datasize = sourcesize; + } else { + /* + * Read the new executable off disk + */ + efi_status = load_image(li, &data, &datasize, PathName); - if (efi_status == EFI_SUCCESS) { - /* Image validates - start it */ - Print(L"Starting file via StartImage\n"); - efi_status = uefi_call_wrapper(BS->StartImage, 3, grub_handle, NULL, - NULL); - uefi_call_wrapper(BS->UnloadImage, 1, grub_handle); - goto done; + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to load image\n"); + goto done; + } } - efi_status = load_grub(li, &data, &datasize, PathName); + /* + * We need to modify the loaded image protocol entry before running + * the new binary, so back it up + */ + CopyMem(&li_bak, li, sizeof(li_bak)); + + /* + * Verify and, if appropriate, relocate and execute the executable + */ + efi_status = handle_image(data, datasize, li); if (efi_status != EFI_SUCCESS) { - Print(L"Failed to load grub\n"); + Print(L"Failed to load image\n"); + CopyMem(li, &li_bak, sizeof(li_bak)); goto done; } - CopyMem(&li_bak, li, sizeof(li_bak)); + /* + * The binary is trusted and relocated. Run it + */ + efi_status = uefi_call_wrapper(entry_point, 2, image_handle, systab); - efi_status = handle_grub(data, datasize, li); + /* + * Restore our original loaded image values + */ + CopyMem(li, &li_bak, sizeof(li_bak)); +done: + if (PathName) + FreePool(PathName); + + if (data) + FreePool(data); + + return efi_status; +} + +/* + * Load and run grub. If that fails because grub isn't trusted, load and + * run MokManager. + */ +EFI_STATUS init_grub(EFI_HANDLE image_handle) +{ + EFI_STATUS efi_status; + + efi_status = start_image(image_handle, SECOND_STAGE); + + if (efi_status != EFI_SUCCESS) + efi_status = start_image(image_handle, MOK_MANAGER); +done: + + return efi_status; +} + +/* + * Copy the boot-services only MokList variable to the runtime-accessible + * MokListRT variable. It's not marked NV, so the OS can't modify it. + */ +EFI_STATUS mirror_mok_list() +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS efi_status; + UINT32 attributes; + void *Data = NULL; + UINTN DataSize = 0; + + efi_status = get_variable(L"MokList", shim_lock_guid, &attributes, + &DataSize, &Data); if (efi_status != EFI_SUCCESS) { - Print(L"Failed to load grub\n"); - CopyMem(li, &li_bak, sizeof(li_bak)); goto done; } - efi_status = uefi_call_wrapper(entry_point, 3, image_handle, systab); + efi_status = uefi_call_wrapper(RT->SetVariable, 5, L"MokListRT", + &shim_lock_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS + | EFI_VARIABLE_RUNTIME_ACCESS, + DataSize, Data); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to set MokListRT %d\n", efi_status); + } - CopyMem(li, &li_bak, sizeof(li_bak)); done: - return efi_status; } +/* + * Check if a variable exists + */ +static BOOLEAN check_var(CHAR16 *varname) +{ + EFI_STATUS efi_status; + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + UINTN size = sizeof(UINT32); + UINT32 MokVar; + UINT32 attributes; + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, varname, + &shim_lock_guid, &attributes, + &size, (void *)&MokVar); + + if (efi_status == EFI_SUCCESS || efi_status == EFI_BUFFER_TOO_SMALL) + return TRUE; + + return FALSE; +} + +/* + * If the OS has set any of these variables we need to drop into MOK and + * handle them appropriately + */ +EFI_STATUS check_mok_request(EFI_HANDLE image_handle) +{ + EFI_STATUS efi_status; + + if (check_var(L"MokNew") || check_var(L"MokSB") || + check_var(L"MokPW") || check_var(L"MokAuth")) { + efi_status = start_image(image_handle, MOK_MANAGER); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to start MokManager\n"); + return efi_status; + } + } + + return EFI_SUCCESS; +} + +/* + * Verify that MokSBState is valid, and if appropriate set insecure mode + */ + +static EFI_STATUS check_mok_sb (void) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + EFI_STATUS status = EFI_SUCCESS; + void *MokSBState = NULL; + UINTN MokSBStateSize = 0; + UINT32 attributes; + + status = get_variable(L"MokSBState", shim_lock_guid, &attributes, + &MokSBStateSize, &MokSBState); + + if (status != EFI_SUCCESS) + return EFI_ACCESS_DENIED; + + /* + * Delete and ignore the variable if it's been set from or could be + * modified by the OS + */ + if (attributes & EFI_VARIABLE_RUNTIME_ACCESS) { + Print(L"MokSBState is compromised! Clearing it\n"); + if (LibDeleteVariable(L"MokSBState", &shim_lock_guid) != EFI_SUCCESS) { + Print(L"Failed to erase MokSBState\n"); + } + status = EFI_ACCESS_DENIED; + } else { + if (*(UINT8 *)MokSBState == 1) { + insecure_mode = 1; + } + } + + return status; +} + EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *passed_systab) { EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; @@ -924,18 +1319,62 @@ EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *passed_systab) EFI_HANDLE handle = NULL; EFI_STATUS efi_status; + /* + * Set up the shim lock protocol so that grub and MokManager can + * call back in and use shim functions + */ shim_lock_interface.Verify = shim_verify; + shim_lock_interface.Hash = generate_hash; + shim_lock_interface.Context = read_header; systab = passed_systab; + /* + * Ensure that gnu-efi functions are available + */ InitializeLib(image_handle, systab); + /* + * Check whether the user has configured the system to run in + * insecure mode + */ + check_mok_sb(); + + /* + * Tell the user that we're in insecure mode if necessary + */ + if (insecure_mode) { + Print(L"Booting in insecure mode\n"); + uefi_call_wrapper(BS->Stall, 1, 2000000); + } + + /* + * Install the protocol + */ uefi_call_wrapper(BS->InstallProtocolInterface, 4, &handle, &shim_lock_guid, EFI_NATIVE_INTERFACE, &shim_lock_interface); + /* + * Enter MokManager if necessary + */ + efi_status = check_mok_request(image_handle); + + /* + * Copy the MOK list to a runtime variable so the kernel can make + * use of it + */ + efi_status = mirror_mok_list(); + + /* + * Hand over control to the second stage bootloader + */ + efi_status = init_grub(image_handle); + /* + * If we're back here then clean everything up before exiting + */ uefi_call_wrapper(BS->UninstallProtocolInterface, 3, handle, &shim_lock_guid, &shim_lock_interface); @@ -1,3 +1,5 @@ +#include "PeImage.h" + #define SHIM_LOCK_GUID \ { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} } @@ -10,6 +12,26 @@ EFI_STATUS IN UINT32 size ); +typedef +EFI_STATUS +(*EFI_SHIM_LOCK_HASH) ( + IN char *data, + IN int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, + UINT8 *sha1hash + ); + +typedef +EFI_STATUS +(*EFI_SHIM_LOCK_CONTEXT) ( + IN VOID *data, + IN unsigned int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context + ); + typedef struct _SHIM_LOCK { EFI_SHIM_LOCK_VERIFY Verify; + EFI_SHIM_LOCK_HASH Hash; + EFI_SHIM_LOCK_CONTEXT Context; } SHIM_LOCK; diff --git a/signature.h b/signature.h index d2a8843e..722dbe64 100644 --- a/signature.h +++ b/signature.h @@ -1,5 +1,6 @@ #define SHA256_DIGEST_SIZE 32 +EFI_GUID EfiHashSha1Guid = { 0x826ca512, 0xcf10, 0x4ac9, {0xb1, 0x87, 0xbe, 0x1, 0x49, 0x66, 0x31, 0xbd }}; EFI_GUID EfiHashSha256Guid = { 0xc1c41626, 0x504c, 0x4092, {0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28 }}; EFI_GUID EfiCertX509Guid = { 0xa5c059a1, 0x94e4, 0x4aa7, {0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72 }}; @@ -12,7 +13,7 @@ typedef struct { /// The format of the signature is defined by the SignatureType. /// UINT8 SignatureData[1]; -} EFI_SIGNATURE_DATA; +} __attribute__ ((packed)) EFI_SIGNATURE_DATA; typedef struct { /// @@ -39,4 +40,4 @@ typedef struct { /// An array of signatures. Each signature is SignatureSize bytes in length. /// EFI_SIGNATURE_DATA Signatures[][SignatureSize]; /// -} EFI_SIGNATURE_LIST; +} __attribute__ ((packed)) EFI_SIGNATURE_LIST; |
