// SPDX-License-Identifier: GPL-2.0+ /* * Menu-driven UEFI Secure Boot Key Maintenance * * Copyright (c) 2022 Masahisa Kojima, Linaro Limited */ #include #include #include #include #include #include #include #include #include #include #include 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; }