From: Simon Glass <simon.glass@canonical.com> Add tests for the bootctl passphrase entry UI and TKey-based LUKS unlock flow: - check_passphrase(): Tests the passphrase textline widget, verifying character input, backspace handling, and passphrase retrieval - prepare_tkey_test(): Sets up the TKey emulator with a test pubkey and configures app mode to test replugging scenarios - try_tkey_unlock(): Tests the complete TKey unlock flow including passphrase entry and LUKS partition decryption - bootctl_logic_tkey: Full integration test for TKey-based encrypted boot with mouse click interactions Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/bootctl/bootctl.c | 433 +++++++++++++++++++++++++++++++++++- 1 file changed, 431 insertions(+), 2 deletions(-) diff --git a/test/boot/bootctl/bootctl.c b/test/boot/bootctl/bootctl.c index 6c12cf708e0..f251cd18507 100644 --- a/test/boot/bootctl/bootctl.c +++ b/test/boot/bootctl/bootctl.c @@ -16,18 +16,26 @@ #include <bootstd.h> #include <dm.h> #include <expo.h> +#include <menu.h> +#include <mouse.h> #include <os.h> +#include <tkey.h> #include "bootctl_common.h" #include <bootctl/logic.h> #include <bootctl/measure.h> #include <bootctl/oslist.h> #include <bootctl/state.h> #include <bootctl/ui.h> +#include <dm/device-internal.h> #include <dm/lists.h> #include <test/ut.h> #include <test/video.h> #include "../bootstd_common.h" - +#include "../../../boot/bootflow_internal.h" +#include "../../../boot/scene_internal.h" +#include "../bootstd_common.h" +#include "../expo_common.h" +// /* test that expected devices are available and can be probed */ static int bootctl_base(struct unit_test_state *uts) { @@ -333,6 +341,97 @@ static int bootctl_simple_measure(struct unit_test_state *uts) } BOOTCTL_TEST(bootctl_simple_measure, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); +/** + * check_passphrase() - Test passphrase functionality for an encrypted item + * + * @uts: Test state + * @ui_dev: UI device to test + * @seq: Sequence number of the encrypted bootflow item + * Return: 0 if OK, -ve on error + */ +static int check_passphrase(struct unit_test_state *uts, + struct udevice *ui_dev, int seq) +{ + struct bc_ui_priv *uc_priv = dev_get_uclass_priv(ui_dev); + const char *retrieved_passphrase = NULL; + struct scene_obj *label_obj, *edit_obj; + struct scene_obj_textline *tline; + struct scene *scn = uc_priv->scn; + bool selected; + int seq_out; + + /* Show passphrase for the specified item (this also opens it) */ + ut_assertok(bc_ui_show_pass(ui_dev, seq, true)); + ut_assertok(bc_ui_render(ui_dev)); + + /* Verify passphrase textline and its child objects are now visible */ + tline = scene_obj_find(scn, ITEM_PASS + seq, SCENEOBJT_TEXTLINE); + ut_assertnonnull(tline); + ut_asserteq(false, tline->obj.flags & SCENEOF_HIDE); + ut_assert(tline->obj.flags & SCENEOF_OPEN); + + /* Verify the scene's highlight is set to the passphrase textline */ + ut_asserteq(ITEM_PASS + seq, scn->highlight_id); + + label_obj = scene_obj_find(scn, ITEM_PASS_LABEL + seq, SCENEOBJT_NONE); + ut_assertnonnull(label_obj); + ut_asserteq(false, label_obj->flags & SCENEOF_HIDE); + + edit_obj = scene_obj_find(scn, ITEM_PASS_EDIT + seq, SCENEOBJT_NONE); + ut_assertnonnull(edit_obj); + ut_asserteq(false, edit_obj->flags & SCENEOF_HIDE); + + /* Type 't', 'e', 's', 't' - each poll processes one character */ + ut_asserteq(4, console_in_puts("test")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("t", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("te", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("tes", abuf_data(&tline->buf)); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("test", abuf_data(&tline->buf)); + + /* Send backspace to remove one character */ + ut_asserteq(1, console_in_puts("\b")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("tes", abuf_data(&tline->buf)); + + /* Re-add the 't' and verify */ + ut_asserteq(1, console_in_puts("t")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_asserteq_str("test", abuf_data(&tline->buf)); + + /* Send return key to submit - should close textline and select */ + ut_asserteq(1, console_in_puts("\n")); + ut_assertok(bc_ui_poll(ui_dev, &seq_out, &selected)); + ut_assert(selected); + ut_asserteq(seq, seq_out); + + /* Verify we can retrieve the passphrase */ + ut_assertok(bc_ui_get_pass(ui_dev, seq, &retrieved_passphrase)); + ut_assertnonnull(retrieved_passphrase); + ut_asserteq_str("test", retrieved_passphrase); + + /* + * Verify the LUKS partition unlock would be attempted. In a real + * scenario, this would call luks_unlock(), but for the test we just + * verify the passphrase was correctly captured and the UI state + * indicates selection was made (which triggers the unlock logic) + */ + + /* Test hiding the passphrase field */ + ut_assertok(bc_ui_show_pass(ui_dev, seq, false)); + ut_assertok(bc_ui_render(ui_dev)); + + /* Verify all three objects are now hidden */ + ut_asserteq(true, tline->obj.flags & SCENEOF_HIDE); + ut_asserteq(true, label_obj->flags & SCENEOF_HIDE); + ut_asserteq(true, edit_obj->flags & SCENEOF_HIDE); + + return 0; +} + static int check_multiboot_ui(struct unit_test_state *uts, struct bootstd_priv *std) { @@ -457,6 +556,11 @@ static int check_multiboot_ui(struct unit_test_state *uts, } } + /* + * Test passphrase functionality for mmc11 (item 0, which is encrypted) + */ + ut_assertok(check_passphrase(uts, ui_dev, 0)); + membuf_dispose(&buf1); membuf_dispose(&buf2); membuf_dispose(&buf3); @@ -497,4 +601,329 @@ static int bootctl_multiboot_ui(struct unit_test_state *uts) return 0; } -BOOTCTL_TEST(bootctl_multiboot_ui, UTF_DM | UTF_SCAN_FDT); +BOOTCTL_TEST(bootctl_multiboot_ui, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/** + * click_os() - Click on an OS in the bootctl UI + * + * @uts: Unit test state + * @lpriv: Logic private data + * @seq: Sequence number of the OS to click + * Return: 0 if OK, -ve on error + */ +static int click_os(struct unit_test_state *uts, struct logic_priv *lpriv, + int seq) +{ + struct bc_ui_priv *uc_priv; + struct scene_obj *obj; + struct scene *scn; + struct expo *exp; + + uc_priv = dev_get_uclass_priv(lpriv->ui); + scn = uc_priv->scn; + exp = uc_priv->expo; + + /* Get the position of ITEM_DESC + seq and queue a click there */ + obj = scene_obj_find(scn, ITEM_DESC + seq, SCENEOBJT_NONE); + ut_assertnonnull(obj); + /* Click halfway along the object, 5 pixels from the top */ + ut_assertok(mouse_queue_click_for_test(exp->mouse, + obj->bbox.x0 + (obj->bbox.x1 - + obj->bbox.x0) / 2, + obj->bbox.y0 + 5)); + + return 0; +} + +/** + * prepare_tkey_test() - Prepare bootctl logic for TKey unlock testing + * + * This helper sets up the complete test environment including: + * - Preparing the logic and finding bootflows + * - Configuring TKey emulator with test pubkey + * - Setting TKey to app mode to test replugging + * - Starting the logic and polling to find OSes + * - Verifying encrypted bootflows were found + * + * @uts: Unit test state + * @logic: Bootctl logic device + * @emul_out: Returns the TKey emulator device + * @test_pubkey: Public key to configure in emulator + * Return: 0 on success, -ve on error + */ +static int prepare_tkey_test(struct unit_test_state *uts, + struct udevice *logic, + struct udevice **emul_out, + const u8 *test_pubkey) +{ + struct logic_priv *lpriv = dev_get_priv(logic); + struct udevice *emul; + + /* + * Prepare the logic. TKey device will be found automatically in + * tkey_poll() when needed (uses first device, which is tkey-emul) + */ + ut_assertok(bc_logic_prepare(logic)); + ut_assertnonnull(lpriv->ui); + ut_assertnonnull(lpriv->oslist); + + /* + * Configure the emulator to return a pubkey that matches the test + * LUKS image. The test image was created with this specific TKey. + * Get the emulator device to configure it. + */ + ut_assertok(uclass_get_device_by_name(UCLASS_TKEY, "tkey-emul", + &emul)); + ut_assertok(tkey_emul_set_pubkey_for_test(emul, test_pubkey)); + + /* + * Put TKey into app mode. This will force the unlock logic to + * request replugging the TKey. + */ + ut_assertok(tkey_emul_set_app_mode_for_test(emul, true)); + + /* Start the logic */ + ut_assertok(bc_logic_start(logic)); + + /* + * Override the TKey device to use the emulator. logic_start() finds + * the first device, but we want to use tkey-emul for testing. + */ + lpriv->tkey = emul; + + /* Poll twice to find both OSes (no delays, so completes quickly) */ + ut_assertok(bc_logic_poll(logic)); + ut_assertok(bc_logic_poll(logic)); + + /* Verify both OSes were found */ + ut_asserteq(2, lpriv->osinfo.count); + + /* First OS should be mmc13 and should be marked as encrypted */ + ut_asserteq_str("mmc13.bootdev.part_1", + alist_getw(&lpriv->osinfo, 0, + struct osinfo)->bflow.name); + ut_assert(alist_getw(&lpriv->osinfo, 0, struct osinfo)->bflow.flags & + BOOTFLOWF_ENCRYPTED); + + /* Verify TKey is enabled (device will be found later in tkey_poll) */ + ut_assert(lpriv->opt_tkey); + + *emul_out = emul; + return 0; +} + +/** + * try_tkey_unlock() - Try to unlock with TKey using a passphrase + * + * @uts: Unit test state + * @logic: Logic device + * @emul: TKey emulator device + * @test_pubkey: Expected public key (or NULL to keep wrong key for failure + * test) + * @passphrase: Passphrase to enter + * @load_iterations_out: Pointer to store load iteration count + * Return: 0 if OK, -ve on error + */ +static int try_tkey_unlock(struct unit_test_state *uts, struct udevice *logic, + struct udevice *emul, const u8 *test_pubkey, + const char *passphrase, int *load_iterations_out) +{ + struct logic_priv *lpriv = dev_get_priv(logic); + int load_iterations; + int i; + + /* Verify passphrase is being requested */ + ut_asserteq(UNS_WAITING_PASS, lpriv->ustate); + ut_asserteq(0, lpriv->selected_seq); + + /* Type the passphrase - each poll processes one character */ + ut_asserteq(strlen(passphrase), console_in_puts(passphrase)); + for (i = 0; i < strlen(passphrase); i++) + ut_assertok(bc_logic_poll(logic)); + + /* Press return to submit the passphrase */ + ut_asserteq(1, console_in_puts("\n")); + + /* Poll to process return - should transition to UNS_TKEY_START */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_START, lpriv->ustate); + + /* Poll - should transition to UNS_TKEY_WAIT_INSERT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_INSERT, lpriv->ustate); + + /* Poll - TKey should be detected, transition to UNS_TKEY_INSERTED */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_INSERTED, lpriv->ustate); + + /* + * Poll - TKey is in app mode, should request removal + * Transition to UNS_TKEY_WAIT_REMOVE + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_REMOVE, lpriv->ustate); + + /* Simulate TKey removal by disconnecting the emulator */ + ut_assertok(tkey_emul_set_connected_for_test(emul, false)); + + /* Poll - should detect removal, transition to UNS_TKEY_WAIT_INSERT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_WAIT_INSERT, lpriv->ustate); + + /* Simulate TKey reinsertion (reconnect the device) */ + ut_assertok(tkey_emul_set_connected_for_test(emul, true)); + + /* + * Poll - TKey should be detected again, transition to + * UNS_TKEY_INSERTED + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_INSERTED, lpriv->ustate); + + /* + * After reprobe, the emulator gets new priv data. + * Set the pubkey if provided (for success), or skip it (for failure) + */ + if (test_pubkey) + ut_assertok(tkey_emul_set_pubkey_for_test(emul, test_pubkey)); + + /* Poll - should start loading, transition to UNS_TKEY_LOADING */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_LOADING, lpriv->ustate); + + /* Poll while TKey app is loading */ + load_iterations = 0; + while (lpriv->ustate == UNS_TKEY_LOADING) { + ut_assertok(bc_logic_poll(logic)); + load_iterations++; + /* Exact count: 28KB / 127 bytes */ + ut_assert(load_iterations <= 221); + } + + /* Verify loading completed - should be in UNS_TKEY_READY */ + ut_asserteq(UNS_TKEY_READY, lpriv->ustate); + ut_asserteq(221, load_iterations); + + if (load_iterations_out) + *load_iterations_out = load_iterations; + + /* Poll - should derive key and transition to UNS_TKEY_UNLOCK */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_TKEY_UNLOCK, lpriv->ustate); + + /* Poll - should perform unlock and transition to UNS_UNLOCK_RESULT */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_UNLOCK_RESULT, lpriv->ustate); + + /* Poll - should process result */ + ut_assertok(bc_logic_poll(logic)); + + return 0; +} + +/* test TKey unlock with logic device - wrong then correct passphrase */ +static int bootctl_logic_tkey(struct unit_test_state *uts) +{ + /* Correct pubkey matching emulator default - produces valid disk key */ + const u8 test_pubkey[32] = { + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f + }; + /* + * Wrong pubkey - produces an invalid disk key for testing unlock + * failure + */ + const u8 wrong_pubkey[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + struct udevice *emul, *logic, *dev; + struct logic_priv *lpriv; + ofnode root, node; + + test_set_skip_delays(true); + bootstd_reset_usb(); + + /* Enable mmc13 device which has the TKey-encrypted partition */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc13"); + ut_assert(ofnode_valid(node)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false)); + + /* Get the logic device */ + ut_assertok(bootctl_get_dev(UCLASS_BOOTCTL, &logic)); + lpriv = dev_get_priv(logic); + + /* Enable TKey support and disable autoboot */ + lpriv->opt_tkey = true; + lpriv->opt_autoboot = false; + + /* Set boot order to include mmc13 before prepare */ + lpriv->opt_labels = "mmc13 usb3"; + + /* Prepare the test environment and verify encrypted bootflows found */ + ut_assertok(prepare_tkey_test(uts, logic, &emul, test_pubkey)); + + /* Queue a click on the first OS (seq 0) to select it */ + ut_assertok(click_os(uts, lpriv, 0)); + + /* Poll the logic - should process the click and ask for passphrase */ + ut_assertok(bc_logic_poll(logic)); + + /* + * First, test wrong passphrase to verify UNS_BAD_PASS state. + * Use wrong_pubkey to simulate a TKey producing an invalid disk key. + */ + ut_assertok(try_tkey_unlock(uts, logic, emul, wrong_pubkey, "wrongpw", + NULL)); + + /* Unlock should fail, transition to UNS_BAD_PASS */ + ut_asserteq(UNS_BAD_PASS, lpriv->ustate); + + /* + * Poll while in error display state - should remain in UNS_BAD_PASS + * Error timeout is checked but we skip delays in tests + */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_BAD_PASS, lpriv->ustate); + + /* + * Advance time past the error timeout (5 seconds) to trigger + * transition back to UNS_IDLE + */ + timer_test_add_offset(6000); /* 6 seconds */ + + /* Poll - error timeout should expire, transition to UNS_IDLE */ + ut_assertok(bc_logic_poll(logic)); + ut_asserteq(UNS_IDLE, lpriv->ustate); + + /* Click on the OS again to re-select it */ + ut_assertok(click_os(uts, lpriv, 0)); + + /* Poll - should process click and ask for passphrase again */ + ut_assertok(bc_logic_poll(logic)); + + /* + * Now type the correct passphrase. The test image was created with + * USS "test" which produces the pubkey configured in the emulator + * above. + */ + ut_assertok(try_tkey_unlock(uts, logic, emul, test_pubkey, "test", + NULL)); + + /* Unlock should succeed, transition to UNS_OK */ + ut_asserteq(UNS_OK, lpriv->ustate); + + /* Verify TKey device was found and used */ + ut_assertnonnull(lpriv->tkey); + ut_assert(lpriv->tkey_present); + + test_set_skip_delays(false); + + return 0; +} +BOOTCTL_TEST(bootctl_logic_tkey, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0