| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Menu-driven UEFI Secure Boot Key Maintenance | |
| * | |
| * Copyright (c) 2022 Masahisa Kojima, Linaro Limited | |
| */ | |
| #include <ansi.h> | |
| #include <charset.h> | |
| #include <hexdump.h> | |
| #include <log.h> | |
| #include <malloc.h> | |
| #include <menu.h> | |
| #include <efi_loader.h> | |
| #include <efi_config.h> | |
| #include <efi_variable.h> | |
| #include <crypto/pkcs7_parser.h> | |
| struct eficonfig_sig_data { | |
| struct efi_signature_list *esl; | |
| struct efi_signature_data *esd; | |
| struct list_head list; | |
| u16 *varname; | |
| }; | |
| enum efi_sbkey_signature_type { | |
| SIG_TYPE_X509 = 0, | |
| SIG_TYPE_HASH, | |
| SIG_TYPE_CRL, | |
| SIG_TYPE_RSA2048, | |
| }; | |
| struct eficonfig_sigtype_to_str { | |
| efi_guid_t sig_type; | |
| char *str; | |
| enum efi_sbkey_signature_type type; | |
| }; | |
| static const struct eficonfig_sigtype_to_str sigtype_to_str[] = { | |
| {EFI_CERT_X509_GUID, "X509", SIG_TYPE_X509}, | |
| {EFI_CERT_SHA256_GUID, "SHA256", SIG_TYPE_HASH}, | |
| {EFI_CERT_X509_SHA256_GUID, "X509_SHA256 CRL", SIG_TYPE_CRL}, | |
| {EFI_CERT_X509_SHA384_GUID, "X509_SHA384 CRL", SIG_TYPE_CRL}, | |
| {EFI_CERT_X509_SHA512_GUID, "X509_SHA512 CRL", SIG_TYPE_CRL}, | |
| /* U-Boot does not support the following signature types */ | |
| /* {EFI_CERT_RSA2048_GUID, "RSA2048", SIG_TYPE_RSA2048}, */ | |
| /* {EFI_CERT_RSA2048_SHA256_GUID, "RSA2048_SHA256", SIG_TYPE_RSA2048}, */ | |
| /* {EFI_CERT_SHA1_GUID, "SHA1", SIG_TYPE_HASH}, */ | |
| /* {EFI_CERT_RSA2048_SHA_GUID, "RSA2048_SHA", SIG_TYPE_RSA2048 }, */ | |
| /* {EFI_CERT_SHA224_GUID, "SHA224", SIG_TYPE_HASH}, */ | |
| /* {EFI_CERT_SHA384_GUID, "SHA384", SIG_TYPE_HASH}, */ | |
| /* {EFI_CERT_SHA512_GUID, "SHA512", SIG_TYPE_HASH}, */ | |
| }; | |
| /** | |
| * file_have_auth_header() - check file has EFI_VARIABLE_AUTHENTICATION_2 header | |
| * @buf: pointer to file | |
| * @size: file size | |
| * Return: true if file has auth header, false otherwise | |
| */ | |
| static bool file_have_auth_header(void *buf, efi_uintn_t size) | |
| { | |
| struct efi_variable_authentication_2 *auth = buf; | |
| if (auth->auth_info.hdr.wCertificateType != WIN_CERT_TYPE_EFI_GUID) | |
| return false; | |
| if (guidcmp(&auth->auth_info.cert_type, &efi_guid_cert_type_pkcs7)) | |
| return false; | |
| return true; | |
| } | |
| /** | |
| * file_is_null_key() - check the file is an authenticated and signed null key | |
| * | |
| * @auth: pointer to the file | |
| * @size: file size | |
| * @null_key: pointer to store the result | |
| * Return: status code | |
| */ | |
| static efi_status_t file_is_null_key(struct efi_variable_authentication_2 *auth, | |
| efi_uintn_t size, bool *null_key) | |
| { | |
| efi_uintn_t auth_size = | |
| sizeof(auth->time_stamp) + auth->auth_info.hdr.dwLength; | |
| if (size < auth_size) | |
| return EFI_INVALID_PARAMETER; | |
| *null_key = (size == auth_size); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| * eficonfig_process_enroll_key() - enroll key into signature database | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t eficonfig_process_enroll_key(void *data) | |
| { | |
| u32 attr; | |
| char *buf = NULL; | |
| efi_uintn_t size; | |
| efi_status_t ret; | |
| bool null_key = false; | |
| struct efi_file_handle *f = NULL; | |
| struct efi_device_path *full_dp = NULL; | |
| struct eficonfig_select_file_info file_info; | |
| file_info.current_path = calloc(1, EFICONFIG_FILE_PATH_BUF_SIZE); | |
| if (!file_info.current_path) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto out; | |
| } | |
| ret = eficonfig_process_select_file(&file_info); | |
| if (ret != EFI_SUCCESS) | |
| goto out; | |
| full_dp = eficonfig_create_device_path(file_info.dp_volume, file_info.current_path); | |
| if (!full_dp) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto out; | |
| } | |
| f = efi_file_from_path(full_dp); | |
| if (!f) { | |
| ret = EFI_NOT_FOUND; | |
| goto out; | |
| } | |
| size = 0; | |
| ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, NULL)); | |
| if (ret != EFI_BUFFER_TOO_SMALL) | |
| goto out; | |
| buf = malloc(size); | |
| if (!buf) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto out; | |
| } | |
| ret = EFI_CALL(f->getinfo(f, &efi_file_info_guid, &size, buf)); | |
| if (ret != EFI_SUCCESS) | |
| goto out; | |
| size = ((struct efi_file_info *)buf)->file_size; | |
| free(buf); | |
| if (!size) { | |
| eficonfig_print_msg("ERROR! File is empty."); | |
| ret = EFI_INVALID_PARAMETER; | |
| goto out; | |
| } | |
| buf = malloc(size); | |
| if (!buf) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto out; | |
| } | |
| ret = EFI_CALL(f->read(f, &size, buf)); | |
| if (ret != EFI_SUCCESS) { | |
| eficonfig_print_msg("ERROR! Failed to read file."); | |
| goto out; | |
| } | |
| if (!file_have_auth_header(buf, size)) { | |
| eficonfig_print_msg("ERROR! Invalid file format. Only .auth variables is allowed."); | |
| ret = EFI_INVALID_PARAMETER; | |
| goto out; | |
| } | |
| ret = file_is_null_key((struct efi_variable_authentication_2 *)buf, | |
| size, &null_key); | |
| if (ret != EFI_SUCCESS) { | |
| eficonfig_print_msg("ERROR! Invalid file format."); | |
| goto out; | |
| } | |
| attr = EFI_VARIABLE_NON_VOLATILE | | |
| EFI_VARIABLE_BOOTSERVICE_ACCESS | | |
| EFI_VARIABLE_RUNTIME_ACCESS | | |
| EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
| /* | |
| * PK can enroll only one certificate. | |
| * The signed null key is used to clear KEK, db and dbx. | |
| * EFI_VARIABLE_APPEND_WRITE attribute must not be set in these cases. | |
| */ | |
| if (u16_strcmp(data, u"PK") && !null_key) { | |
| efi_uintn_t db_size = 0; | |
| /* check the variable exists. If exists, add APPEND_WRITE attribute */ | |
| ret = efi_get_variable_int(data, efi_auth_var_get_guid(data), NULL, | |
| &db_size, NULL, NULL); | |
| if (ret == EFI_BUFFER_TOO_SMALL) | |
| attr |= EFI_VARIABLE_APPEND_WRITE; | |
| } | |
| ret = efi_set_variable_int((u16 *)data, efi_auth_var_get_guid((u16 *)data), | |
| attr, size, buf, false); | |
| if (ret != EFI_SUCCESS) | |
| eficonfig_print_msg("ERROR! Failed to update signature database"); | |
| out: | |
| free(file_info.current_path); | |
| free(buf); | |
| efi_free_pool(full_dp); | |
| if (f) | |
| EFI_CALL(f->close(f)); | |
| /* return to the parent menu */ | |
| ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
| return ret; | |
| } | |
| /** | |
| * eficonfig_process_show_siglist() - show signature list content | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t eficonfig_process_show_siglist(void *data) | |
| { | |
| u32 i; | |
| struct eficonfig_sig_data *sg = data; | |
| puts(ANSI_CURSOR_HIDE); | |
| puts(ANSI_CLEAR_CONSOLE); | |
| printf(ANSI_CURSOR_POSITION, 1, 1); | |
| printf("\n ** Show Signature Database (%ls) **\n\n" | |
| " Owner GUID:\n" | |
| " %pUL\n", | |
| sg->varname, sg->esd->signature_owner.b); | |
| for (i = 0; i < ARRAY_SIZE(sigtype_to_str); i++) { | |
| if (!guidcmp(&sg->esl->signature_type, &sigtype_to_str[i].sig_type)) { | |
| printf(" Signature Type:\n" | |
| " %s\n", sigtype_to_str[i].str); | |
| switch (sigtype_to_str[i].type) { | |
| case SIG_TYPE_X509: | |
| { | |
| struct x509_certificate *cert_tmp; | |
| cert_tmp = x509_cert_parse(sg->esd->signature_data, | |
| sg->esl->signature_size); | |
| printf(" Subject:\n" | |
| " %s\n" | |
| " Issuer:\n" | |
| " %s\n", | |
| cert_tmp->subject, cert_tmp->issuer); | |
| break; | |
| } | |
| case SIG_TYPE_CRL: | |
| { | |
| u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t) - | |
| sizeof(struct efi_time); | |
| struct efi_time *time = | |
| (struct efi_time *)((u8 *)sg->esd->signature_data + | |
| hash_size); | |
| printf(" ToBeSignedHash:\n"); | |
| print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, | |
| sg->esd->signature_data, hash_size, false); | |
| printf(" TimeOfRevocation:\n" | |
| " %d-%d-%d %02d:%02d:%02d\n", | |
| time->year, time->month, time->day, | |
| time->hour, time->minute, time->second); | |
| break; | |
| } | |
| case SIG_TYPE_HASH: | |
| { | |
| u32 hash_size = sg->esl->signature_size - sizeof(efi_guid_t); | |
| printf(" Hash:\n"); | |
| print_hex_dump(" ", DUMP_PREFIX_NONE, 16, 1, | |
| sg->esd->signature_data, hash_size, false); | |
| break; | |
| } | |
| default: | |
| eficonfig_print_msg("ERROR! Unsupported format."); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| } | |
| } | |
| while (tstc()) | |
| getchar(); | |
| printf("\n\n Press any key to continue"); | |
| getchar(); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| * prepare_signature_list_menu() - create the signature list menu entry | |
| * | |
| * @efimenu: pointer to the efimenu structure | |
| * @varname: pointer to the variable name | |
| * @db: pointer to the variable raw data | |
| * @db_size: variable data size | |
| * @func: callback of each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t prepare_signature_list_menu(struct efimenu *efi_menu, void *varname, | |
| void *db, efi_uintn_t db_size, | |
| eficonfig_entry_func func) | |
| { | |
| u32 num = 0; | |
| efi_uintn_t size; | |
| struct eficonfig_sig_data *sg; | |
| struct efi_signature_list *esl; | |
| struct efi_signature_data *esd; | |
| efi_status_t ret = EFI_SUCCESS; | |
| INIT_LIST_HEAD(&efi_menu->list); | |
| esl = db; | |
| size = db_size; | |
| while (size > 0) { | |
| u32 remain; | |
| esd = (struct efi_signature_data *)((u8 *)esl + | |
| (sizeof(struct efi_signature_list) + | |
| esl->signature_header_size)); | |
| remain = esl->signature_list_size - sizeof(struct efi_signature_list) - | |
| esl->signature_header_size; | |
| for (; remain > 0; remain -= esl->signature_size) { | |
| char buf[37]; | |
| char *title; | |
| if (num >= EFICONFIG_ENTRY_NUM_MAX - 1) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto out; | |
| } | |
| sg = calloc(1, sizeof(struct eficonfig_sig_data)); | |
| if (!sg) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto err; | |
| } | |
| snprintf(buf, sizeof(buf), "%pUL", &esd->signature_owner); | |
| title = strdup(buf); | |
| if (!title) { | |
| free(sg); | |
| ret = EFI_OUT_OF_RESOURCES; | |
| goto err; | |
| } | |
| sg->esl = esl; | |
| sg->esd = esd; | |
| sg->varname = varname; | |
| ret = eficonfig_append_menu_entry(efi_menu, title, func, sg); | |
| if (ret != EFI_SUCCESS) { | |
| free(sg); | |
| free(title); | |
| goto err; | |
| } | |
| esd = (struct efi_signature_data *)((u8 *)esd + esl->signature_size); | |
| num++; | |
| } | |
| size -= esl->signature_list_size; | |
| esl = (struct efi_signature_list *)((u8 *)esl + esl->signature_list_size); | |
| } | |
| out: | |
| ret = eficonfig_append_quit_entry(efi_menu); | |
| err: | |
| return ret; | |
| } | |
| /** | |
| * enumerate_and_show_signature_database() - enumerate and show the signature database | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t enumerate_and_show_signature_database(void *varname) | |
| { | |
| void *db; | |
| char buf[50]; | |
| efi_status_t ret; | |
| efi_uintn_t db_size; | |
| struct efimenu *efi_menu; | |
| struct list_head *pos, *n; | |
| struct eficonfig_entry *entry; | |
| db = efi_get_var(varname, efi_auth_var_get_guid(varname), &db_size); | |
| if (!db) { | |
| eficonfig_print_msg("There is no entry in the signature database."); | |
| return EFI_NOT_FOUND; | |
| } | |
| efi_menu = calloc(1, sizeof(struct efimenu)); | |
| if (!efi_menu) { | |
| free(db); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| ret = prepare_signature_list_menu(efi_menu, varname, db, db_size, | |
| eficonfig_process_show_siglist); | |
| if (ret != EFI_SUCCESS) | |
| goto out; | |
| snprintf(buf, sizeof(buf), " ** Show Signature Database (%ls) **", (u16 *)varname); | |
| ret = eficonfig_process_common(efi_menu, buf, eficonfig_menu_desc, | |
| eficonfig_display_statusline, | |
| eficonfig_print_entry, | |
| eficonfig_choice_entry); | |
| out: | |
| list_for_each_safe(pos, n, &efi_menu->list) { | |
| entry = list_entry(pos, struct eficonfig_entry, list); | |
| free(entry->data); | |
| } | |
| eficonfig_destroy(efi_menu); | |
| free(db); | |
| return ret; | |
| } | |
| /** | |
| * eficonfig_process_show_signature_database() - process show signature database | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t eficonfig_process_show_signature_database(void *data) | |
| { | |
| efi_status_t ret; | |
| while (1) { | |
| ret = enumerate_and_show_signature_database(data); | |
| if (ret != EFI_SUCCESS && ret != EFI_NOT_READY) | |
| break; | |
| } | |
| /* return to the parent menu */ | |
| ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
| return ret; | |
| } | |
| static struct eficonfig_item key_config_menu_items[] = { | |
| {"Enroll New Key", eficonfig_process_enroll_key}, | |
| {"Show Signature Database", eficonfig_process_show_signature_database}, | |
| {"Quit", eficonfig_process_quit}, | |
| }; | |
| /** | |
| * eficonfig_process_set_secure_boot_key() - display the key configuration menu | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| static efi_status_t eficonfig_process_set_secure_boot_key(void *data) | |
| { | |
| u32 i; | |
| efi_status_t ret; | |
| char header_str[32]; | |
| struct efimenu *efi_menu; | |
| for (i = 0; i < ARRAY_SIZE(key_config_menu_items); i++) | |
| key_config_menu_items[i].data = data; | |
| snprintf(header_str, sizeof(header_str), " ** Configure %ls **", (u16 *)data); | |
| while (1) { | |
| efi_menu = eficonfig_create_fixed_menu(key_config_menu_items, | |
| ARRAY_SIZE(key_config_menu_items)); | |
| ret = eficonfig_process_common(efi_menu, header_str, | |
| eficonfig_menu_desc, | |
| eficonfig_display_statusline, | |
| eficonfig_print_entry, | |
| eficonfig_choice_entry); | |
| eficonfig_destroy(efi_menu); | |
| if (ret == EFI_ABORTED) | |
| break; | |
| } | |
| /* return to the parent menu */ | |
| ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
| return ret; | |
| } | |
| static const struct eficonfig_item secure_boot_menu_items[] = { | |
| {"PK", eficonfig_process_set_secure_boot_key, u"PK"}, | |
| {"KEK", eficonfig_process_set_secure_boot_key, u"KEK"}, | |
| {"db", eficonfig_process_set_secure_boot_key, u"db"}, | |
| {"dbx", eficonfig_process_set_secure_boot_key, u"dbx"}, | |
| {"Quit", eficonfig_process_quit}, | |
| }; | |
| /** | |
| * eficonfig_process_secure_boot_config() - display the key list menu | |
| * | |
| * @data: pointer to the data for each entry | |
| * Return: status code | |
| */ | |
| efi_status_t eficonfig_process_secure_boot_config(void *data) | |
| { | |
| efi_status_t ret; | |
| struct efimenu *efi_menu; | |
| while (1) { | |
| char header_str[64]; | |
| snprintf(header_str, sizeof(header_str), | |
| " ** UEFI Secure Boot Key Configuration (SecureBoot : %s) **", | |
| (efi_secure_boot_enabled() ? "ON" : "OFF")); | |
| efi_menu = eficonfig_create_fixed_menu(secure_boot_menu_items, | |
| ARRAY_SIZE(secure_boot_menu_items)); | |
| if (!efi_menu) { | |
| ret = EFI_OUT_OF_RESOURCES; | |
| break; | |
| } | |
| ret = eficonfig_process_common(efi_menu, header_str, | |
| eficonfig_menu_desc, | |
| eficonfig_display_statusline, | |
| eficonfig_print_entry, | |
| eficonfig_choice_entry); | |
| eficonfig_destroy(efi_menu); | |
| if (ret == EFI_ABORTED) | |
| break; | |
| } | |
| /* return to the parent menu */ | |
| ret = (ret == EFI_ABORTED) ? EFI_NOT_READY : ret; | |
| return ret; | |
| } |