test: bootctl: Add passphrase UI and TKey unlock tests
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>
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user