[PATCH 00/19] bootctl: Continue development with TKey functionality
From: Simon Glass <simon.glass@canonical.com> This series integrates the TKey disk-unlock features into the bootctl UI, as a demonstration of how this might work. The user is prompted for a passphrase, which is then used as a user-supplied secret (USS) for the TKey. This series includes support for using a pre-derived master key, so that the TKey emulator can be used in tests. Future work will continue this effort. Simon Glass (19): tpm: Fix missing log size when using bloblist bootctl: Drop unnecessary calls to calculate dimensions bootctl: Set the password flag on the passphrase edit text video: Add a lock image bootctl: Bring in another image bootctl: Allow switching between logos bootctl: Show a lock symbol for locked disks bootctl: Provide passphrase and message objects in the expo bootctl: Enhance the UI to support a TKey bootctl: Provide an extra poll between select and booting bootctl: Clean up some unwanted debugging in the logic bootctl: Add the logic for disk unlock using a TKey bootctl: Fix up the header-inclusion order in the test tkey: Correct handling of the USS tkey: Allow using the selected TKey from luks luks: Add -p flag for pre-derived master key bootctl: Allow unlocking LUKS2 partitions bootctl: Add a TKey for testing bootctl: Enable the tests boot/bootctl/canonical.bmp | Bin 0 -> 41634 bytes boot/bootctl/logic.c | 695 +++++++++++++++++++++++++++++++++- boot/bootctl/multi_ui.c | 118 +++++- boot/bootctl/simple_ui.c | 11 +- boot/bootctl/util.c | 60 +++ boot/bootflow_menu.c | 16 +- cmd/luks.c | 42 +- cmd/tkey.c | 2 +- configs/sandbox_defconfig | 1 + doc/usage/cmd/luks.rst | 18 +- drivers/misc/tkey-uclass.c | 16 +- drivers/misc/tkey_emul.c | 9 +- drivers/video/images/Makefile | 1 + drivers/video/images/lock.bmp | Bin 0 -> 2454 bytes include/bootctl.dtsi | 8 + include/bootctl/logic.h | 58 ++- include/bootctl/ui.h | 80 ++++ include/tkey.h | 7 + lib/tpm_tcg2.c | 1 + test/Kconfig | 1 - test/boot/Makefile | 1 + test/boot/bootctl/bootctl.c | 8 +- test/dm/video.c | 3 +- 23 files changed, 1097 insertions(+), 59 deletions(-) create mode 100644 boot/bootctl/canonical.bmp create mode 100644 drivers/video/images/lock.bmp -- 2.43.0 base-commit: 593009a032cbf0f8ffb7d9cbad609c2d13d089b5 branch: seci
From: Simon Glass <simon.glass@canonical.com> tcg2_platform_get_log() fails to set *sizep when returning a TPM event log from bloblist. This causes tcg2_log_prepare_buffer() to receive a log size of 0, which then causes all tcg2_log_append_check() calls to fail with "log too large" errors since any event size exceeds the zero-sized buffer. This manifests when running measurement tests after other tests that use the TPM event log bloblist (e.g. bootctl tests), as the bloblist entry exists but its size is not properly returned. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> --- lib/tpm_tcg2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tpm_tcg2.c b/lib/tpm_tcg2.c index bcd3423d923..98fa3acef6e 100644 --- a/lib/tpm_tcg2.c +++ b/lib/tpm_tcg2.c @@ -688,6 +688,7 @@ __weak int tcg2_platform_get_log(struct udevice *dev, void **ptrp, u32 *sizep) ulong addr = map_to_sysmem(ptr); *ptrp = map_physmem(addr, size, MAP_NOCACHE); + *sizep = size; return 0; } -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Since scene_calc_dims() is done by scene_arrange(), we don't need to do an explicit call to scene_calc_dims() first. Drop it from both UIs. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/multi_ui.c | 9 --------- boot/bootctl/simple_ui.c | 4 ---- 2 files changed, 13 deletions(-) diff --git a/boot/bootctl/multi_ui.c b/boot/bootctl/multi_ui.c index adf48808c0f..975f9a1b91c 100644 --- a/boot/bootctl/multi_ui.c +++ b/boot/bootctl/multi_ui.c @@ -426,10 +426,6 @@ static int multiboot_ui_add(struct udevice *dev, struct osinfo *info) multiboot_set_item_props(scn, seq, &info->bflow); - ret = expo_calc_dims(upriv->expo); - if (ret) - return log_msg_ret("ecd", ret); - if (lpriv->default_os && !strcmp(lpriv->default_os, info->bflow.os_name)) scene_menu_select_item(scn, OBJ_MENU, ITEM + seq); @@ -498,11 +494,6 @@ static int multiboot_ui_switch_layout(struct udevice *dev) return log_msg_ret("props", ret); } - /* Calculate dimensions then re-arrange */ - ret = expo_calc_dims(upriv->expo); - if (ret) - return log_msg_ret("ecd", ret); - ret = scene_arrange(scn); if (ret) return log_msg_ret("arr", ret); diff --git a/boot/bootctl/simple_ui.c b/boot/bootctl/simple_ui.c index 7fb59c8b8c3..58860138a33 100644 --- a/boot/bootctl/simple_ui.c +++ b/boot/bootctl/simple_ui.c @@ -156,10 +156,6 @@ static int simple_ui_add(struct udevice *dev, struct osinfo *info) if (ret) return log_msg_ret("asn", ret); - ret = expo_calc_dims(upriv->expo); - if (ret) - return log_msg_ret("ecd", ret); - if (lpriv->default_os && !strcmp(lpriv->default_os, info->bflow.os_name)) scene_menu_select_item(scn, OBJ_MENU, ITEM + seq); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The SCENEOF_PASSWORD flag is set on the textline object, but the rendering code checks the flag on the text object being rendered. Set the flag on the edit text object so the passphrase renders as asterisks instead of showing the actual characters. Mark the textline object as hidden while we are here, so it disappears when switching to the simple bootmenu. Co-developed-by: Claude <claude@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootflow_menu.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 01af30c6627..1b740c8d16b 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -45,6 +45,7 @@ static int bootflow_menu_set_item_props(struct scene *scn, scene_obj_set_hide(scn, ITEM_VERIFIED + i, true); ret |= scene_obj_set_hide(scn, ITEM_KEY + i, false); scene_obj_set_hide(scn, ITEM_LOCKED + i, true); + scene_obj_set_hide(scn, ITEM_PASS + i, true); scene_obj_set_hide(scn, ITEM_PASS_LABEL + i, true); scene_obj_set_hide(scn, ITEM_PASS_EDIT + i, true); if (ret) @@ -233,6 +234,7 @@ int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq, { struct menu_priv *priv = exp->priv; struct scene_obj_textline *tline; + struct scene_obj_txt *txt; char str[2], *key; struct scene *scn; uint preview_id; @@ -308,10 +310,11 @@ int bootflow_menu_add(struct expo *exp, struct bootflow *bflow, int seq, snprintf(name, sizeof(name), "item%d.pass.edit", seq); ret = scene_txt_str(scn, name, ITEM_PASS_EDIT + seq, 0, - abuf_data(&tline->buf), NULL); + abuf_data(&tline->buf), &txt); if (ret < 0) return log_msg_ret("ite", -EINVAL); tline->edit_id = ret; + txt->obj.flags |= SCENEOF_PASSWORD; /* Create message text (hidden by default) for success/error feedback */ snprintf(name, sizeof(name), "item%d.pass_msg", seq); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add an image which represents a locked disk. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/video/images/Makefile | 1 + drivers/video/images/lock.bmp | Bin 0 -> 2454 bytes test/dm/video.c | 3 ++- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 drivers/video/images/lock.bmp diff --git a/drivers/video/images/Makefile b/drivers/video/images/Makefile index 8e719691880..31fffc4f66a 100644 --- a/drivers/video/images/Makefile +++ b/drivers/video/images/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_BOOTCTL) += canonical.o obj-$(CONFIG_BOOTCTL) += tick.o obj-$(CONFIG_BOOTCTL) += multipass.o settings.o obj-$(CONFIG_BOOTCTL) += help.o +obj-$(CONFIG_BOOTCTL) += lock.o diff --git a/drivers/video/images/lock.bmp b/drivers/video/images/lock.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8e9bd3a06ead93cb9ea26a52cad0272b38ad9f2d GIT binary patch literal 2454 zcmd5;YfO_@7=FQRI&3N0();E6+J3!KsKptg(;*U^8)OCR1yHmXiVRw6OGOY7wFN<2 zn6NQnBA`{~b<yZ%vL(9ApIeM3x+Tk!VazTX=QM1Yi|O9BPDqQmCHt|H_nh;*m*@G; zrAbd-(R(jLv@O6<g`NAPCq)s!rI!GhvtvD4h(q|lZol5Yf9KBKe`QFCTgAP5zudh3 z``MEZPLJL@5&HSVBR`Ig+&(%ycI41lXy|*iqeHhwN6>C#3Jb98+^L61L1LU#=+Nzj z3;GTE{h8V}jj}}{Z<I^Dq{Kx?>IkueBpjr~DI;A<%A;0n(WqLo=LL@p{tzdE?z`Pr zGc$S&`jA$=U#;rWsDnCfFhdj2X=4`5(sj?v>M>;X8uWdOa!zE=>+9{hmZ-DWe@U&_ zrPCfzQa(zyLndvOOL0jJB;l13^)k{!5^hrBL5!wW`Y7qv4*%zgI&HfyA~93jtDssb zd9#Arrc^X3s7AG7D*`3eq)|0%)vY@1uFMSoE7|?%A|+90_l}RG5{F9J9{sM95Z6e= zc8SO)5?Dk+i&$vJF^X+sQKgLZiiMR(Nz~c7{kTYI(<pb!rFA4xLlV^_VJ9Uv8CfZp z+9_ETC3{;TcPOdNTGbZ4ZjYSYgp@>`?M)**Zkb$KFBV!wg7th}DW7NJaf|s}BbW0Q zk89*{ih10X0^VvNzf>Z!b2+A#ro)LkO<NCQnUug=Hj9PjV&NL0z$6kBi-bn8sDu!k zNTN(iRLG@PB~_iNZNP%=%6L25%pSU5I<f&k1<NT=tvkto(G=Yw66zEanmxGnb8S z`U)<m2p1;gmob?I4c`7loox*RG<sGVBb%P8XQb*<85#ydNl%s2>C&_`MOvC_&YTPu zQ_p72=dg2l+*g^*`Au5}5_Qg;xz^QrA<%K|gZ@w5u3#{5yr=7Ahkw}bi&(oq6x??r zu=l9P(|d5>vq0zhK<C8^XQ%ibK2`4C-<v{DD_d9bG!g^r*H^6DU=fn4(CC@C)Rb;l zFfeO&s%cGmTz1teQ^iKBfKZMcI~|vr((TySH7kWyymED1)@reom2W_u!^cj=rKWWE z2H#<(vvU?M8XLPcxuCthZCSw^GF9e3)EO8Y)@2!JG+Le0`RLIfF{$zK@y2Z}B7$0y zUwHQXCzJK3!8Oj!b6K3ZEOvgu>-MS|dv$HWiXy&9iq`7iHw`f^b>+&Hy3HQFAsaV1 zGlfoP%;5^eix%g$?(Y2R>W#SK=emQ#q20bcexI+ctqm{n(&fv~rTl*i0FsAltq#u% zxd0Aly}M+29<jExjF|Zoq<{eEK!M(D^|+QAjfH>z=Oxa+-GHP>^tvvhm6)G4Lbp|V ztN=*_R^?h<?s~)<5ie+{cSZ2~h*{Q6W*g#@h%?-{K*V21;+7b`8^KjkoQx1yZd1uJ z+#3rqQ8fu$CSmI)w*zbDV~yPAaH4+l+8DM)aB~DZYQ4C&Xp8KSR_R=|5@$JfAuNX# zPy|LGzye;VhFYix9nMaSc%TIHdC%bUqkJQ>EXb+`D>Cw-4jf>P@*#2uSc7fiY(4gD zLo}2`%L91vyKwj+1u(M*;Bj|2{O52u{CEUl#<%!VaZd2-h5&NLG2T59lOF<L_y8_^ zF%h%$0esw!ubyvQX16)|of$YL+wA}kjseh1@#S+BAf-HZZkUVY;qXlW6P{J!mjDO) X0TflJz3v(No`r;`=QeH4lk@uvkMaW6 literal 0 HcmV?d00001 diff --git a/test/dm/video.c b/test/dm/video.c index a2c654831b9..6029fa3d5cc 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -1305,13 +1305,14 @@ static int dm_test_video_images(struct unit_test_state *uts) ut_assert_nextline("bgrt 43926"); ut_assert_nextline("canonical 14258"); ut_assert_nextline("help 1782"); + ut_assert_nextline("lock 2454"); ut_assert_nextline("multipass 4378"); ut_assert_nextline("riscos_arrow 3798"); ut_assert_nextline("settings 1782"); ut_assert_nextline("tick 822"); ut_assert_nextline("u_boot 6932"); ut_assert_skip_to_line(""); - ut_assert_nextline("Total images: 8"); + ut_assert_nextline("Total images: 9"); ut_assert_console_end(); return 0; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add an image for the Canonical logo, so we can see how the screen layout looks. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/canonical.bmp | Bin 0 -> 41634 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 boot/bootctl/canonical.bmp diff --git a/boot/bootctl/canonical.bmp b/boot/bootctl/canonical.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2e1eca267b044ce2d44a846c1a94889845839a66 GIT binary patch literal 41634 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 literal 0 HcmV?d00001 -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The bootctl tests are currently disabled due to some image incompatibilities: the multi UI uses one image and the simple UI uses a different one. Update the logic to switch between these logos when the layout changes. For now, use the U-Boot logo in both cases. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/multi_ui.c | 4 ++++ boot/bootctl/simple_ui.c | 7 +++++-- boot/bootflow_menu.c | 11 ++++++++++- include/bootctl.dtsi | 3 +++ test/boot/bootctl/bootctl.c | 4 ++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/boot/bootctl/multi_ui.c b/boot/bootctl/multi_ui.c index 975f9a1b91c..f663bcdac61 100644 --- a/boot/bootctl/multi_ui.c +++ b/boot/bootctl/multi_ui.c @@ -215,6 +215,10 @@ static int multiboot_ui_set_props(struct udevice *dev, struct scene *scn, scene_obj_set_hide(scn, OBJ_AUTOBOOT, !lpriv->opt_autoboot); if (upriv->logo) { + ret = scene_img_set_data(scn, OBJ_U_BOOT_LOGO, upriv->logo, + upriv->logo_size); + if (ret) + return log_msg_ret("log", ret); ret = scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1045, 10); if (ret) return log_msg_ret("lop", ret); diff --git a/boot/bootctl/simple_ui.c b/boot/bootctl/simple_ui.c index 58860138a33..34f46f04838 100644 --- a/boot/bootctl/simple_ui.c +++ b/boot/bootctl/simple_ui.c @@ -92,8 +92,11 @@ static int simple_ui_show(struct udevice *dev) abuf_printf(buf, "Boot control"); if (upriv->logo) { - ret = scene_img_set_data(scn, OBJ_U_BOOT_LOGO, - upriv->logo, upriv->logo_size); + const void *logo; + int size; + + logo = video_get_u_boot_logo(&size); + ret = scene_img_set_data(scn, OBJ_U_BOOT_LOGO, logo, size); if (ret) return log_msg_ret("log", ret); ret = scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1135, 10); diff --git a/boot/bootflow_menu.c b/boot/bootflow_menu.c index 1b740c8d16b..909694f6e7b 100644 --- a/boot/bootflow_menu.c +++ b/boot/bootflow_menu.c @@ -80,9 +80,18 @@ int bootflow_menu_set_props(struct expo *exp, struct scene *scn, bool has_logo, 1366, 60); ret |= scene_obj_set_halign(scn, OBJ_MENU_TITLE, SCENEOA_CENTRE); - if (has_logo) + if (has_logo) { + const void *logo; + int size; + ret |= scene_obj_set_pos(scn, OBJ_U_BOOT_LOGO, 1165, 100); + logo = video_get_u_boot_logo(&size); + ret |= scene_img_set_data(scn, OBJ_U_BOOT_LOGO, logo, size); + if (ret) + return log_msg_ret("log", ret); + } + ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1A, 0, 590, 1366, 590 + 40); ret |= scene_obj_set_bbox(scn, OBJ_PROMPT1B, 0, 620, diff --git a/include/bootctl.dtsi b/include/bootctl.dtsi index 05032a924e1..4070fa32c5f 100644 --- a/include/bootctl.dtsi +++ b/include/bootctl.dtsi @@ -120,6 +120,9 @@ "bootctl,ui"; graphical = "if-available"; textual = "if-available"; +#ifdef CANONICAL_LOGO + logo = /incbin/("/home/sglass/u/boot/bootctl/canonical.bmp"); +#endif }; ui-simple { diff --git a/test/boot/bootctl/bootctl.c b/test/boot/bootctl/bootctl.c index ab9aa28a668..532a73d6336 100644 --- a/test/boot/bootctl/bootctl.c +++ b/test/boot/bootctl/bootctl.c @@ -396,7 +396,7 @@ static int check_multiboot_ui(struct unit_test_state *uts, ut_assertok(bc_ui_add(ui_dev, &info[0])); ut_assertok(bc_ui_add(ui_dev, &info[1])); ut_assertok(bc_ui_render(ui_dev)); - ut_asserteq(16645, video_compress_fb(uts, vid_dev, false)); + ut_asserteq(13702, video_compress_fb(uts, vid_dev, false)); /* dump after render - buf2 is golden for multiboot_ui */ uc_priv = dev_get_uclass_priv(ui_dev); @@ -435,7 +435,7 @@ static int check_multiboot_ui(struct unit_test_state *uts, /* switch back to multiboot UI style and check against buf2 */ ut_assertok(bc_ui_switch_layout(ui_dev)); ut_assertok(bc_ui_render(ui_dev)); - ut_asserteq(16645, video_compress_fb(uts, vid_dev, false)); + ut_asserteq(13702, video_compress_fb(uts, vid_dev, false)); /* dump after switch back to multiboot - buf4 should match buf2 */ ut_assertok(membuf_new(&buf4, 4096)); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> When an OS is using disk encryption, show a lock symbol next to it. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/multi_ui.c | 8 ++++++++ test/boot/bootctl/bootctl.c | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/boot/bootctl/multi_ui.c b/boot/bootctl/multi_ui.c index f663bcdac61..11e5a46c911 100644 --- a/boot/bootctl/multi_ui.c +++ b/boot/bootctl/multi_ui.c @@ -140,11 +140,15 @@ static int multiboot_set_item_props(struct scene *scn, int i, IMAGES_Y + 5); scene_obj_set_pos(scn, ITEM_VERIFIED + i, x + BOX_MARGIN + 40 + 32, IMAGES_Y + 80 + 21); + scene_obj_set_pos(scn, ITEM_LOCKED + i, + x + BOX_W - BOX_MARGIN - 24, IMAGES_Y + BOX_MARGIN); ret |= scene_obj_set_hide(scn, ITEM_PREVIEW + i, false); ret |= scene_obj_set_hide(scn, ITEM_BOX + i, false); ret |= scene_obj_set_hide(scn, ITEM_VERSION_NAME + i, false); ret |= scene_obj_set_hide(scn, ITEM_VERIFIED + i, false); + ret |= scene_obj_set_hide(scn, ITEM_LOCKED + i, + !(bflow->flags & BOOTFLOWF_ENCRYPTED)); /* Hide key in multiboot mode (not used with mouse) */ ret |= scene_obj_set_hide(scn, ITEM_KEY + i, true); @@ -422,6 +426,10 @@ static int multiboot_ui_add(struct udevice *dev, struct osinfo *info) logo = video_image_getptr(tick); ret |= scene_img(scn, "verified", ITEM_VERIFIED + seq, logo, NULL); + + logo = video_image_getptr(lock); + ret |= scene_img(scn, "locked", ITEM_LOCKED + seq, logo, + NULL); } ret = bootstd_get_priv(&std); diff --git a/test/boot/bootctl/bootctl.c b/test/boot/bootctl/bootctl.c index 532a73d6336..ff1a9722fe3 100644 --- a/test/boot/bootctl/bootctl.c +++ b/test/boot/bootctl/bootctl.c @@ -396,7 +396,7 @@ static int check_multiboot_ui(struct unit_test_state *uts, ut_assertok(bc_ui_add(ui_dev, &info[0])); ut_assertok(bc_ui_add(ui_dev, &info[1])); ut_assertok(bc_ui_render(ui_dev)); - ut_asserteq(13702, video_compress_fb(uts, vid_dev, false)); + ut_asserteq(17279, video_compress_fb(uts, vid_dev, false)); /* dump after render - buf2 is golden for multiboot_ui */ uc_priv = dev_get_uclass_priv(ui_dev); @@ -435,7 +435,7 @@ static int check_multiboot_ui(struct unit_test_state *uts, /* switch back to multiboot UI style and check against buf2 */ ut_assertok(bc_ui_switch_layout(ui_dev)); ut_assertok(bc_ui_render(ui_dev)); - ut_asserteq(13702, video_compress_fb(uts, vid_dev, false)); + ut_asserteq(17279, video_compress_fb(uts, vid_dev, false)); /* dump after switch back to multiboot - buf4 should match buf2 */ ut_assertok(membuf_new(&buf4, 4096)); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Set up a textline object to allow the user to enter a passphrase and a text object to display messages for the user. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/multi_ui.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/boot/bootctl/multi_ui.c b/boot/bootctl/multi_ui.c index 11e5a46c911..f4f0bd281fe 100644 --- a/boot/bootctl/multi_ui.c +++ b/boot/bootctl/multi_ui.c @@ -143,12 +143,21 @@ static int multiboot_set_item_props(struct scene *scn, int i, scene_obj_set_pos(scn, ITEM_LOCKED + i, x + BOX_W - BOX_MARGIN - 24, IMAGES_Y + BOX_MARGIN); + /* Position passphrase textline at bottom of box, hidden by default */ + scene_obj_set_pos(scn, ITEM_PASS + i, x + BOX_MARGIN, + IMAGES_Y + BOX_H - 60); + + /* Position message below passphrase, hidden by default */ + scene_obj_set_pos(scn, ITEM_PASS_MSG + i, x + BOX_MARGIN, + IMAGES_Y + BOX_H - 35); + ret |= scene_obj_set_hide(scn, ITEM_PREVIEW + i, false); ret |= scene_obj_set_hide(scn, ITEM_BOX + i, false); ret |= scene_obj_set_hide(scn, ITEM_VERSION_NAME + i, false); ret |= scene_obj_set_hide(scn, ITEM_VERIFIED + i, false); ret |= scene_obj_set_hide(scn, ITEM_LOCKED + i, !(bflow->flags & BOOTFLOWF_ENCRYPTED)); + ret |= scene_obj_set_hide(scn, ITEM_PASS + i, false); /* Hide key in multiboot mode (not used with mouse) */ ret |= scene_obj_set_hide(scn, ITEM_KEY + i, true); @@ -216,6 +225,10 @@ static int multiboot_ui_set_props(struct udevice *dev, struct scene *scn, scene_obj_set_pos(scn, OBJ_PROMPT2, MAIN_X, 180); scene_obj_set_halign(scn, OBJ_PROMPT2, SCENEOA_LEFT); + /* Position passphrase input at bottom and hide by default */ + scene_obj_set_pos(scn, ITEM_PASS, MAIN_X, 750); + scene_obj_set_hide(scn, ITEM_PASS, true); + scene_obj_set_hide(scn, OBJ_AUTOBOOT, !lpriv->opt_autoboot); if (upriv->logo) { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> When unlocking an encrypted disk with a TKey we need a few more UI operations: - allow the password-entry textline to be shown/hidden - allow showing instructions to the user, as well as unlock result - obtain the password entered by the user Add these to the API and implement them in the multi UI. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/multi_ui.c | 84 +++++++++++++++++++++++++++++++++++++++++ boot/bootctl/util.c | 60 +++++++++++++++++++++++++++++ include/bootctl/ui.h | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+) diff --git a/boot/bootctl/multi_ui.c b/boot/bootctl/multi_ui.c index f4f0bd281fe..5145e4357cb 100644 --- a/boot/bootctl/multi_ui.c +++ b/boot/bootctl/multi_ui.c @@ -577,6 +577,86 @@ static int multiboot_ui_of_to_plat(struct udevice *dev) return 0; } +static int multiboot_ui_show_pass(struct udevice *dev, int seq, bool show) +{ + struct bc_ui_priv *upriv = dev_get_uclass_priv(dev); + struct scene *scn = upriv->scn; + int ret; + + scene_obj_set_hide(scn, ITEM_PASS + seq, !show); + scene_obj_set_hide(scn, ITEM_PASS_LABEL + seq, !show); + scene_obj_set_hide(scn, ITEM_PASS_EDIT + seq, !show); + + if (show) { + struct scene_obj_textline *tline; + char *buf; + + /* Clear the passphrase buffer for retry */ + tline = scene_obj_find(scn, ITEM_PASS + seq, + SCENEOBJT_TEXTLINE); + if (!tline) + return log_msg_ret("tln", -ENOENT); + buf = abuf_data(&tline->buf); + *buf = '\0'; + + /* Set highlight and open the textline for editing */ + scene_set_highlight_id(scn, ITEM_PASS + seq); + ret = scene_set_open(scn, ITEM_PASS + seq, true); + if (ret) + return log_msg_ret("sop", ret); + } else { + /* Close the textline */ + ret = scene_set_open(scn, ITEM_PASS + seq, false); + if (ret) + return log_msg_ret("sop", ret); + } + + return 0; +} + +static int multiboot_ui_get_pass(struct udevice *dev, int seq, + const char **passp) +{ + struct bc_ui_priv *upriv = dev_get_uclass_priv(dev); + struct scene *scn = upriv->scn; + struct scene_obj_textline *tline; + + tline = scene_obj_find(scn, ITEM_PASS + seq, SCENEOBJT_TEXTLINE); + if (!tline) + return log_msg_ret("tln", -ENOENT); + + *passp = abuf_data(&tline->buf); + + return 0; +} + +static int multiboot_ui_show_pass_msg(struct udevice *dev, int seq, bool show) +{ + struct bc_ui_priv *upriv = dev_get_uclass_priv(dev); + struct scene *scn = upriv->scn; + + scene_obj_set_hide(scn, ITEM_PASS_MSG + seq, !show); + + return 0; +} + +static int multiboot_ui_set_pass_msg(struct udevice *dev, int seq, + const char *msg) +{ + struct bc_ui_priv *upriv = dev_get_uclass_priv(dev); + struct expo *exp = upriv->expo; + struct abuf *buf; + int ret; + + ret = expo_edit_str(exp, STR_PASS_MSG + seq, NULL, &buf); + if (ret) + return log_msg_ret("spm", ret); + + abuf_set(buf, (void *)msg, strlen(msg) + 1); + + return 0; +} + static struct bc_ui_ops ops = { .print = multiboot_ui_print, .show = multiboot_ui_show, @@ -584,6 +664,10 @@ static struct bc_ui_ops ops = { .render = multiboot_ui_render, .poll = multiboot_ui_poll, .switch_layout = multiboot_ui_switch_layout, + .show_pass = multiboot_ui_show_pass, + .get_pass = multiboot_ui_get_pass, + .show_pass_msg = multiboot_ui_show_pass_msg, + .set_pass_msg = multiboot_ui_set_pass_msg, }; static const struct udevice_id multiboot_ui_ids[] = { diff --git a/boot/bootctl/util.c b/boot/bootctl/util.c index 48912c67f21..790489ca937 100644 --- a/boot/bootctl/util.c +++ b/boot/bootctl/util.c @@ -110,6 +110,66 @@ int bc_ui_switch_layout(struct udevice *dev) return 0; } +int bc_ui_show_pass(struct udevice *dev, int seq, bool show) +{ + struct bc_ui_ops *ops = bc_ui_get_ops(dev); + int ret; + + if (!ops->show_pass) + return -ENOSYS; + + ret = ops->show_pass(dev, seq, show); + if (ret) + return log_msg_ret("bsp", ret); + + return 0; +} + +int bc_ui_get_pass(struct udevice *dev, int seq, const char **passp) +{ + struct bc_ui_ops *ops = bc_ui_get_ops(dev); + int ret; + + if (!ops->get_pass) + return -ENOSYS; + + ret = ops->get_pass(dev, seq, passp); + if (ret) + return log_msg_ret("bgp", ret); + + return 0; +} + +int bc_ui_show_pass_msg(struct udevice *dev, int seq, bool show) +{ + struct bc_ui_ops *ops = bc_ui_get_ops(dev); + int ret; + + if (!ops->show_pass_msg) + return -ENOSYS; + + ret = ops->show_pass_msg(dev, seq, show); + if (ret) + return log_msg_ret("bse", ret); + + return 0; +} + +int bc_ui_set_pass_msg(struct udevice *dev, int seq, const char *msg) +{ + struct bc_ui_ops *ops = bc_ui_get_ops(dev); + int ret; + + if (!ops->set_pass_msg) + return -ENOSYS; + + ret = ops->set_pass_msg(dev, seq, msg); + if (ret) + return log_msg_ret("bsm", ret); + + return 0; +} + void bc_oslist_setup_iter(struct oslist_iter *iter) { memset(iter, '\0', sizeof(struct oslist_iter)); diff --git a/include/bootctl/ui.h b/include/bootctl/ui.h index 4f8e08a00c2..5e592e3dce2 100644 --- a/include/bootctl/ui.h +++ b/include/bootctl/ui.h @@ -99,6 +99,46 @@ struct bc_ui_ops { * Return 0 if OK, -ve on error */ int (*switch_layout)(struct udevice *dev); + + /** + * show_pass() - Show or hide the passphrase input field + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @show: true to show the pass field, false to hide it + * Return 0 if OK, -ve on error + */ + int (*show_pass)(struct udevice *dev, int seq, bool show); + + /** + * get_pass() - Get the passphrase entered by the user + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @passp: Returns pointer to the passphrase string + * Return 0 if OK, -ve on error + */ + int (*get_pass)(struct udevice *dev, int seq, const char **passp); + + /** + * show_pass_msg() - Show or hide the pass message + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @show: true to show the message, false to hide it + * Return 0 if OK, -ve on error + */ + int (*show_pass_msg)(struct udevice *dev, int seq, bool show); + + /** + * set_pass_msg() - Set the pass message text + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @msg: Message text to display + * Return 0 if OK, -ve on error + */ + int (*set_pass_msg)(struct udevice *dev, int seq, const char *msg); }; #define bc_ui_get_ops(dev) ((struct bc_ui_ops *)(dev)->driver->ops) @@ -148,4 +188,44 @@ int bc_ui_poll(struct udevice *dev, int *seqp, bool *selectedp); */ int bc_ui_switch_layout(struct udevice *dev); +/** + * bc_ui_show_pass() - Show or hide the pass input field + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @show: true to show the pass field, false to hide it + * Return 0 if OK, -ve on error + */ +int bc_ui_show_pass(struct udevice *dev, int seq, bool show); + +/** + * bc_ui_get_pass() - Get the pass entered by the user + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @passp: Returns pointer to the pass string + * Return 0 if OK, -ve on error + */ +int bc_ui_get_pass(struct udevice *dev, int seq, const char **passp); + +/** + * bc_ui_show_pass_msg() - Show or hide the pass message + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @show: true to show the message, false to hide it + * Return 0 if OK, -ve on error + */ +int bc_ui_show_pass_msg(struct udevice *dev, int seq, bool show); + +/** + * bc_ui_set_pass_msg() - Set the pass message text + * + * @dev: Display device + * @seq: Sequence number of the bootflow item + * @msg: Message text to display + * Return 0 if OK, -ve on error + */ +int bc_ui_set_pass_msg(struct udevice *dev, int seq, const char *msg); + #endif -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Selecting an OS may cause the UI to change, so give it a chance to show those changes before booting. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/logic.c | 14 ++++++++++++++ include/bootctl/logic.h | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/boot/bootctl/logic.c b/boot/bootctl/logic.c index e2ec2c679ec..d3ee7b2fa6c 100644 --- a/boot/bootctl/logic.c +++ b/boot/bootctl/logic.c @@ -242,8 +242,22 @@ static int logic_poll(struct udevice *dev) struct osinfo *os; os = alist_getw(&priv->osinfo, seq, struct osinfo); + if (!os) + return log_msg_ret("gos", -ENOENT); + priv->ready_to_boot = false; + priv->selected_seq = seq; + } + + if (priv->ready_to_boot) { + struct osinfo *os; + + seq = priv->selected_seq; + os = alist_getw(&priv->osinfo, seq, struct osinfo); + if (!os) + return log_msg_ret("gbo", -ENOENT); log_info("Selected %d: %s\n", seq, os->bflow.os_name); + priv->ready_to_boot = false; /* * try to read the images first; some methods don't support * this diff --git a/include/bootctl/logic.h b/include/bootctl/logic.h index 1aefac29128..a1de68235ac 100644 --- a/include/bootctl/logic.h +++ b/include/bootctl/logic.h @@ -37,10 +37,10 @@ struct udevice; * @default_os: name of the default OS to boot * @osinfo: List of OSes to show * @refresh: true if we need to refresh the UI because something has changed + * @selected_seq: sequence number of OS waiting for passphrase, or -1 if none + * @ready_to_boot: true if success message shown, ready to boot on next poll * * @iter: oslist iterator, used to find new OSes - * @selected: index of selected OS in osinfo alist, or -1 if none has been - * selected yet * @meas: TPM-measurement device * @oslist: provides OSes to boot; we iterate through each osinfo driver to find * all OSes @@ -68,6 +68,8 @@ struct logic_priv { const char *default_os; struct alist osinfo; bool refresh; + int selected_seq; + bool ready_to_boot; struct oslist_iter iter; struct udevice *meas; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Tidy up the code style for checking the result of read_images() and drop some commented-out debug lines. Also fix a shadowed variable while we are here. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/logic.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/boot/bootctl/logic.c b/boot/bootctl/logic.c index d3ee7b2fa6c..be00c965b66 100644 --- a/boot/bootctl/logic.c +++ b/boot/bootctl/logic.c @@ -74,8 +74,6 @@ static int logic_start(struct udevice *dev) int ret; if (priv->opt_persist_state) { - int ret; - /* read in our state */ ret = bc_state_load(priv->state); if (ret) @@ -263,17 +261,12 @@ static int logic_poll(struct udevice *dev) * this */ ret = read_images(dev, os); - if (ret && ret != -ENOSYS) { - if (ret) - return log_msg_ret("lri", ret); - } + if (ret && ret != -ENOSYS) + return log_msg_ret("lri", ret); ret = prepare_for_boot(dev, os); if (ret) return log_msg_ret("lpb", ret); - /* debugging */ - // return -ESHUTDOWN; - /* boot OS */ ret = bootflow_boot(&os->bflow); if (ret) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The unlock process is quite different with a TKey, since we need to load the signing app with the user's passphrase as the USS. If the TKey is not already in firmware mode we must prompt the user to remove and re-insert it. The easiest way to implement this is via a state machine. Add the required logic: - detect key presence and absence - prompt for removal and insertion - send the app, while keeping the UI responsive - unlock the disk once the TKey provides a key - report success or failure to the user Ensure that the logic is only used if CONFIG_TKEY and CONFIG_LUKS are both enabled. Co-developed-by: Claude <claude@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/logic.c | 670 +++++++++++++++++++++++++++++++++++++++- include/bootctl/logic.h | 52 ++++ 2 files changed, 721 insertions(+), 1 deletion(-) diff --git a/boot/bootctl/logic.c b/boot/bootctl/logic.c index be00c965b66..96cc270ae2c 100644 --- a/boot/bootctl/logic.c +++ b/boot/bootctl/logic.c @@ -10,8 +10,12 @@ #include <bootctl.h> #include <dm.h> +#include <hexdump.h> #include <log.h> +#include <luks.h> +#include <part.h> #include <time.h> +#include <tkey.h> #include <version.h> #include <bootctl/logic.h> #include <bootctl/measure.h> @@ -20,9 +24,12 @@ #include <bootctl/ui.h> #include <bootctl/util.h> #include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <u-boot/sha256.h> enum { - COUNTDOWN_INTERVAL_MS = 1000, /* inteval between autoboot updates */ + COUNTDOWN_INTERVAL_MS = 1000, /* interval between autoboot updates */ + ERROR_TIMEOUT_MS = 5000, /* timeout for error message display */ }; static int logic_prepare(struct udevice *dev) @@ -64,6 +71,17 @@ static int logic_prepare(struct udevice *dev) return log_msg_ret("blo", ret); } + /* Find TKey device if enabled (test can override this) */ + if (priv->opt_tkey) { + ret = uclass_find_first_device(UCLASS_TKEY, &priv->tkey); + if (ret || !priv->tkey) { + log_debug("TKey not found at startup\n"); + } else { + log_debug("TKey '%s'\n", priv->tkey->name); + /* Device found but not probed yet - not present */ + priv->tkey_present = false; + } + } return 0; } @@ -181,6 +199,615 @@ static int read_images(struct udevice *dev, struct osinfo *osinfo) return 0; } +/** + * show_unlock_error() - Display error message and update UI state + * + * Helper function to show an error message, display error state, + * and hide the pass prompt. + * + * @priv: Logic private data + * @seq: Sequence number of the selected OS + * @msg: Error message to display + * Return: 0 if OK, -ve on error + */ +static int show_unlock_error(struct logic_priv *priv, int seq, const char *msg) +{ + int ret; + + ret = bc_ui_set_pass_msg(priv->ui, seq, msg); + if (ret && ret != -ENOSYS) + return log_msg_ret("sem", ret); + ret = bc_ui_show_pass_msg(priv->ui, seq, true); + if (ret && ret != -ENOSYS) + return log_msg_ret("see", ret); + ret = bc_ui_show_pass(priv->ui, seq, false); + if (ret) + return log_msg_ret("hsp", ret); + + return 0; +} + +/** + * start_tkey_load() - Start TKey app loading + * + * Starts the app loading process. Expects TKey device to be already probed + * and present. + * + * @dev: Logic device + * @pass: User passphrase to use as USS + * Return: 0 on success or if needs replug (state set appropriately), + * -ve on error + */ +static int start_tkey_load(struct udevice *dev, const char *pass) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + ret = tkey_load_start(&priv->tkey_load_ctx, priv->tkey, + (const u8 *)__signer_1_0_0_begin, + TKEY_SIGNER_SIZE, (const u8 *)pass, strlen(pass)); + if (ret) + return ret; + log_debug("Started TKey app loading (%zx bytes)\n", TKEY_SIGNER_SIZE); + + return 0; +} + +/** + * derive_tkey_disk_key() - Derive disk encryption key from TKey public key + * + * Gets the public key from the TKey and derives the disk encryption key + * using SHA256. Must match the Python tkey-fde-key.py implementation. + * + * @dev: Logic device + * Return: 0 on success, -ve on error + */ +static int derive_tkey_disk_key(struct udevice *dev) +{ + struct logic_priv *priv = dev_get_priv(dev); + u8 pubkey[TKEY_PUBKEY_SIZE]; + char pubkey_hex[TKEY_PUBKEY_SIZE * 2 + 1]; + sha256_context ctx; + int ret; + + /* Get public key from the loaded app */ + ret = tkey_get_pubkey(priv->tkey, pubkey); + if (ret) { + log_warning("Failed to get TKey public key (err=%dE)\n", ret); + return ret; + } + + /* + * Derive disk encryption key from public key using SHA256 + * Must match Python tkey-fde-key.py implementation which does: + * hashlib.sha256(pubkey.encode()).digest() + * + * This converts the binary public key to hex string, + * then hashes the string bytes. + */ + bin2hex(pubkey_hex, pubkey, TKEY_PUBKEY_SIZE); + + sha256_starts(&ctx); + sha256_update(&ctx, (const u8 *)pubkey_hex, TKEY_PUBKEY_SIZE * 2); + sha256_finish(&ctx, priv->tkey_disk_key); + + log_info("TKey disk key derived successfully\n"); + + return 0; +} + +/** + * perform_tkey_unlock() - Perform TKey-based LUKS unlock + * + * This function performs LUKS unlock using the TKey-derived key as binary + * passphrase material. Expects TKey to be already loaded and key derived. + * + * @dev: Logic device + * @os: OS information containing the encrypted bootflow + * @seq: Sequence number of the selected OS + * @master_key: Buffer to store the unlocked master key + * @key_size: Pointer to key size (input: buffer size, output: actual size) + * Return: 0 if unlock succeeded, -ENOENT if there is no encrypted partition, + * other -ve on other error + */ +static int perform_tkey_unlock(struct udevice *dev, struct osinfo *os, int seq, + u8 *master_key, u32 *key_sizep) +{ + struct logic_priv *priv = dev_get_priv(dev); + struct disk_partition pinfo; + int ret; + + /* TKey key should already be derived at this point */ + assert(priv->ustate == UNS_TKEY_UNLOCK); + + /* Get partition info for the encrypted partition (next after boot) */ + ret = part_get_info(dev_get_uclass_plat(os->bflow.blk), + os->bflow.part + 1, &pinfo); + if (ret) { + log_warning("Failed to get partition info (err=%dE)\n", ret); + return -ENOENT; + } + + /* + * Use TKey-derived key as binary passphrase input to LUKS KDF + * The key is treated as binary passphrase material that will be + * processed by PBKDF2/Argon2 just like a text passphrase would be. + * This matches how cryptsetup --key-file works. + */ + log_info("Using LUKS1 unlock with binary passphrase\n"); + ret = luks_unlock(os->bflow.blk, &pinfo, priv->tkey_disk_key, + TKEY_DISK_KEY_SIZE, true, master_key, key_sizep); + if (ret) + return log_msg_ret("htu", ret); + + return 0; +} + +/** + * handle_idle() - Set up the passphrase prompt UI + * + * This handles the UNS_IDLE state, showing the passphrase prompt and + * transitioning to UNS_WAITING_PASS. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on success, -ve on error + */ +static int handle_idle(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + /* show passphrase prompt and hide any error */ + ret = bc_ui_show_pass_msg(priv->ui, seq, false); + if (ret && ret != -ENOSYS) + return log_msg_ret("hse", ret); + ret = bc_ui_show_pass(priv->ui, seq, true); + if (ret) + return log_msg_ret("lsp", ret); + priv->ustate = UNS_WAITING_PASS; + priv->selected_seq = seq; + priv->refresh = true; + + return 0; +} + +/** + * handle_waiting_pass() - Handle waiting for passphrase entry + * + * This handles the UNS_WAITING_PASS state, getting the passphrase and + * showing the "Unlocking..." message, then transitioning to either + * UNS_UNLOCK_NORMAL or UNS_TKEY_START based on configuration. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on success, -ve on error + */ +static int handle_waiting_pass(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + const char *pass; + int ret; + + /* Get pass and show "Unlocking..." message */ + ret = bc_ui_get_pass(priv->ui, seq, &pass); + if (ret) { + log_warning("Failed to get pass (err=%dE)\n", ret); + priv->ustate = UNS_IDLE; + return -EAGAIN; /* Return to menu */ + } + + /* Show "Unlocking..." message */ + ret = bc_ui_set_pass_msg(priv->ui, seq, "Unlocking..."); + if (ret && ret != -ENOSYS) + return log_msg_ret("spu", ret); + ret = bc_ui_show_pass_msg(priv->ui, seq, true); + if (ret && ret != -ENOSYS) + return log_msg_ret("ssu", ret); + ret = bc_ui_show_pass(priv->ui, seq, false); + if (ret) + return log_msg_ret("hsp", ret); + + /* Select unlock path based on TKey option */ + if (priv->opt_tkey) + priv->ustate = UNS_TKEY_START; + else + priv->ustate = UNS_UNLOCK_NORMAL; + priv->refresh = true; + + return 0; +} + +/** + * handle_unlock_normal() - Perform normal LUKS unlock with direct passphrase + * + * This handles the UNS_UNLOCK_NORMAL state, performing LUKS unlock + * with direct passphrase. + * + * @dev: Logic device + * @os: OS information + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_unlock_normal(struct udevice *dev, struct osinfo *os, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + struct disk_partition pinfo; + u8 master_key[128]; + u32 key_size = sizeof(master_key); + const char *pass; + int ret; + + /* Get pass for direct LUKS unlock */ + ret = bc_ui_get_pass(priv->ui, seq, &pass); + if (ret) { + log_warning("Failed to get pass (err=%dE)\n", ret); + priv->ustate = UNS_IDLE; + return 0; + } + + /* Get partition info for the encrypted partition (next after boot) */ + ret = part_get_info(dev_get_uclass_plat(os->bflow.blk), + os->bflow.part + 1, &pinfo); + if (ret) { + log_warning("Failed to get partition info (err=%dE)\n", ret); + priv->ustate = UNS_IDLE; + return 0; + } + + /* Try to unlock with the pass */ + ret = luks_unlock(os->bflow.blk, &pinfo, (const u8 *)pass, strlen(pass), + false, master_key, &key_size); + + /* Store result and transition to result handling state */ + priv->unlock_result = ret; + priv->ustate = UNS_UNLOCK_RESULT; + priv->refresh = true; + + return 0; +} + +/** + * handle_tkey_wait_remove() - Handle TKey removal wait state + * + * This handles the UNS_TKEY_WAIT_REMOVE state, waiting for TKey to be + * physically removed and then transitioning to UNS_TKEY_WAIT_INSERT. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_wait_remove(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + /* Check if TKey is still present by checking app mode */ + ret = tkey_in_app_mode(priv->tkey); + if (ret < 0) { + /* TKey removed (error accessing device) */ + log_debug("TKey removed, cleaning up device\n"); + device_remove(priv->tkey, DM_REMOVE_NORMAL); + priv->tkey_present = false; + log_debug("TKey removed, ready for next attempt\n"); + + /* Show replug message */ + ret = show_unlock_error(priv, seq, "Please insert TKey"); + if (ret) + return log_msg_ret("rpe", ret); + priv->refresh = true; + priv->ustate = UNS_TKEY_WAIT_INSERT; + + return 0; + } + + return 0; +} + +/** + * handle_tkey_start() - Handle TKey unlock start + * + * Checks if TKey device is available and transitions to wait for insert state. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_start(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + + /* Check if TKey device is available */ + if (!priv->tkey) { + log_err("TKey device not found\n"); + show_unlock_error(priv, seq, "TKey not found"); + return -ENODEV; + } + priv->ustate = UNS_TKEY_WAIT_INSERT; + + return 0; +} + +/** + * handle_tkey_wait_insert() - Handle TKey insertion wait state + * + * This handles the UNS_TKEY_WAIT_INSERT state, probing for TKey device + * and transitioning to UNS_TKEY_INSERTED when found. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: -EAGAIN to continue waiting or transition to next state + */ +static int handle_tkey_wait_insert(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + log_debug("Probing TKey device\n"); + ret = device_probe(priv->tkey); + if (ret) { + /* Probe failed - device not yet inserted */ + log_debug("TKey not inserted yet, waiting\n"); + priv->ustate = UNS_TKEY_WAIT_INSERT; + return 0; + } + /* Probe succeeded - device is present */ + log_debug("TKey probed successfully\n"); + priv->tkey_present = true; + priv->ustate = UNS_TKEY_INSERTED; + + return 0; +} + +/** + * handle_tkey_inserted() - Handle TKey inserted state + * + * This handles the UNS_TKEY_INSERTED state, starting the TKey app loading + * process with the user's passphrase. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_inserted(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + const char *pass; + int ret; + + /* Get passphrase for TKey derivation */ + ret = bc_ui_get_pass(priv->ui, seq, &pass); + if (ret) { + log_warning("Failed to get pass (err=%dE)\n", ret); + priv->ustate = UNS_IDLE; + return 0; /* Return to menu */ + } + + /* Start loading TKey app with USS */ + ret = start_tkey_load(dev, pass); + if (ret == -ENOTSUPP) { + /* TKey in app mode, needs to be replugged */ + log_debug("TKey not in firmware mode, needs replug\n"); + priv->ustate = UNS_TKEY_WAIT_REMOVE; + + /* Show replug message */ + ret = show_unlock_error(priv, seq, "Please remove TKey"); + if (ret) + return log_msg_ret("rpe", ret); + priv->refresh = true; + return 0; + } else if (ret) { + log_warning("Failed to start TKey app load (err=%dE)\n", ret); + return ret; + } + + priv->ustate = UNS_TKEY_LOADING; + + return 0; +} + +/** + * handle_tkey_loading() - Handle TKey app loading state + * + * This handles the UNS_TKEY_LOADING state, sending blocks of the TKey app + * and transitioning to UNS_TKEY_READY when complete. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_loading(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + /* Send 1 block per poll to keep the UI responsive */ + ret = tkey_load_next(&priv->tkey_load_ctx, 1); + if (ret == -EAGAIN) { + char msg[64]; + int percent; + + /* More blocks to send */ + log_debug("TKey loading: %u/%u bytes\n", + priv->tkey_load_ctx.offset, + priv->tkey_load_ctx.app_size); + + /* Show loading progress - round up so 100% will show */ + percent = ((priv->tkey_load_ctx.offset + 1) * 100 + + priv->tkey_load_ctx.app_size - 1) / + priv->tkey_load_ctx.app_size; + if (percent > 100) + percent = 100; + snprintf(msg, sizeof(msg), "Preparing TKey... %d%%", percent); + bc_ui_set_pass_msg(priv->ui, seq, msg); + priv->refresh = true; + return 0; + } + + if (ret) { + log_warning("Failed to load TKey app (err=%dE)\n", ret); + priv->ustate = UNS_TKEY_START; + return ret; + } + + /* Loading complete, now derive disk key */ + log_info("TKey app loaded successfully, deriving disk key\n"); + priv->ustate = UNS_TKEY_READY; + + return 0; +} + +/** + * handle_tkey_ready() - Handle TKey ready state + * + * This handles the UNS_TKEY_READY state, deriving the disk encryption key + * from the TKey public key and transitioning to IN_PROGRESS state. + * + * @dev: Logic device + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_ready(struct udevice *dev, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + int ret; + + /* Derive disk encryption key from TKey public key */ + ret = derive_tkey_disk_key(dev); + if (ret) + return ret; + bc_ui_set_pass_msg(priv->ui, seq, "Unlocking..."); + priv->refresh = true; + + /* Key derived, start unlock */ + priv->ustate = UNS_TKEY_UNLOCK; + + return 0; +} + +/** + * handle_tkey_unlock() - Handle TKey-based LUKS unlock state + * + * This handles the UNS_TKEY_UNLOCK state, performing LUKS unlock with + * TKey-derived key and transitioning to UNS_UNLOCK_RESULT. + * + * @dev: Logic device + * @os: OS information + * @seq: Sequence number of the selected OS + * Return: 0 on normal operation, -ve on error + */ +static int handle_tkey_unlock(struct udevice *dev, struct osinfo *os, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + u8 master_key[128]; + u32 key_size = sizeof(master_key); + int ret; + + ret = perform_tkey_unlock(dev, os, seq, master_key, &key_size); + + /* Store result and transition to result handling state */ + priv->unlock_result = ret; + priv->ustate = UNS_UNLOCK_RESULT; + priv->refresh = true; + + return 0; +} + +/** + * handle_unlock_result() - Handle the result of unlock operation + * + * Processes unlock result, showing either error or success message. + * On error, shows "Incorrect passphrase" and transitions to UNS_BAD_PASS. + * On success, shows "Unlock successful" and transitions to UNS_OK. + * + * @priv: Logic private data + * @seq: Sequence number of the selected OS + * @unlock_ret: Return value from unlock operation + * Return: -EAGAIN always (to wait for next poll) + */ +static int handle_unlock_result(struct logic_priv *priv, int seq, + int unlock_ret) +{ + int ret; + + if (unlock_ret) { + log_warning("Failed to unlock LUKS partition (err=%dE)\n", + unlock_ret); + + /* Set and show error message, hide pass prompt */ + ret = show_unlock_error(priv, seq, "Incorrect passphrase"); + if (ret) + return log_msg_ret("ipe", ret); + /* Display error for 5 seconds before allowing retry */ + priv->time_error = get_timer(0); + priv->ustate = UNS_BAD_PASS; + priv->refresh = true; + return 0; + } + + log_info("LUKS partition unlocked successfully\n"); + /* Set and show success message briefly, hide pass prompt */ + ret = show_unlock_error(priv, seq, "Unlock successful"); + if (ret) + return log_msg_ret("suc", ret); + /* Show success message for one poll cycle, then boot */ + priv->ustate = UNS_OK; + priv->refresh = true; + /* TODO: Create blkmap device for decrypted access */ + + return 0; +} + +static int handle_encrypted_tkey(struct udevice *dev, struct osinfo *os, + int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + + if (!IS_ENABLED(CONFIG_TKEY)) + return -ENOSYS; + + switch (priv->ustate) { + case UNS_TKEY_WAIT_REMOVE: + return handle_tkey_wait_remove(dev, seq); + case UNS_TKEY_START: + return handle_tkey_start(dev, seq); + case UNS_TKEY_WAIT_INSERT: + return handle_tkey_wait_insert(dev, seq); + case UNS_TKEY_INSERTED: + return handle_tkey_inserted(dev, seq); + case UNS_TKEY_LOADING: + return handle_tkey_loading(dev, seq); + case UNS_TKEY_READY: + return handle_tkey_ready(dev, seq); + case UNS_TKEY_UNLOCK: + return handle_tkey_unlock(dev, os, seq); + default: + return -EINVAL; + } +} + +static int handle_encrypted(struct udevice *dev, struct osinfo *os, int seq) +{ + struct logic_priv *priv = dev_get_priv(dev); + + switch (priv->ustate) { + case UNS_IDLE: + return handle_idle(dev, seq); + case UNS_WAITING_PASS: + return handle_waiting_pass(dev, seq); + case UNS_UNLOCK_NORMAL: + return handle_unlock_normal(dev, os, seq); + case UNS_BAD_PASS: + case UNS_OK: + /* These states shouldn't reach here in normal flow */ + return -EINVAL; + case UNS_UNLOCK_RESULT: + return handle_unlock_result(priv, seq, priv->unlock_result); + default: + return handle_encrypted_tkey(dev, os, seq); + } +} + static int logic_poll(struct udevice *dev) { struct logic_priv *priv = dev_get_priv(dev); @@ -207,6 +834,26 @@ static int logic_poll(struct udevice *dev) } } + /* if unlock succeeded - show a message and boot on the next poll */ + if (priv->ustate == UNS_OK) { + /* Success message is set, prepare to boot after rendering */ + priv->ustate = UNS_IDLE; + priv->ready_to_boot = true; + priv->refresh = true; + } + + /* Check if error message display timeout has expired */ + if (priv->ustate == UNS_BAD_PASS && priv->time_error && + get_timer(priv->time_error) > ERROR_TIMEOUT_MS) { + /* Hide error message and allow retry */ + ret = bc_ui_show_pass_msg(priv->ui, priv->selected_seq, false); + if (ret && ret != -ENOSYS) + return log_msg_ret("hse", ret); + priv->time_error = 0; + priv->ustate = UNS_IDLE; + priv->refresh = true; + } + if (priv->autoboot_active && get_timer(priv->start_time) > priv->next_countdown) { ulong secs = get_timer(priv->start_time) / 1000; @@ -230,18 +877,38 @@ static int logic_poll(struct udevice *dev) else if (ret) priv->refresh = true; + /* Ignore menu selection while displaying error message */ + if (selected && priv->ustate == UNS_BAD_PASS) + selected = false; + if (!selected && priv->autoboot_active && !priv->autoboot_remain_s && seq >= 0) { log_info("Selecting %d due to timeout\n", seq); selected = true; } + /* If ready to unlock, trigger selection to continue unlock process */ + if (!selected && priv->ustate != UNS_WAITING_PASS && + priv->ustate != UNS_IDLE && priv->ustate != UNS_BAD_PASS) { + seq = priv->selected_seq; + selected = true; + log_debug("Continuing unlock for seq %d\n", seq); + } + if (selected) { struct osinfo *os; os = alist_getw(&priv->osinfo, seq, struct osinfo); if (!os) return log_msg_ret("gos", -ENOENT); + + /* If encrypted, handle pass entry and unlock */ + if (IS_ENABLED(CONFIG_LUKS) && + (os->bflow.flags & BOOTFLOWF_ENCRYPTED)) { + ret = handle_encrypted(dev, os, seq); + if (ret) + return ret; + } priv->ready_to_boot = false; priv->selected_seq = seq; } @@ -294,6 +961,7 @@ static int logic_of_to_plat(struct udevice *dev) priv->opt_autoboot = ofnode_read_bool(node, "autoboot"); priv->opt_measure = ofnode_read_bool(node, "measure"); priv->opt_slow_refresh = ofnode_read_bool(node, "slow-refresh"); + priv->opt_tkey = ofnode_read_bool(node, "tkey"); return 0; } diff --git a/include/bootctl/logic.h b/include/bootctl/logic.h index a1de68235ac..62a0223d080 100644 --- a/include/bootctl/logic.h +++ b/include/bootctl/logic.h @@ -10,9 +10,43 @@ #define __bootctl_logic_h #include <bootctl/oslist.h> +#include <tkey.h> struct udevice; +/** + * enum unlock_state - State of the disk unlock process + * + * @UNS_IDLE: No unlock in progress + * @UNS_WAITING_PASS: Waiting for user to enter passphrase + * @UNS_UNLOCK_NORMAL: Unlocking with direct passphrase + * @UNS_TKEY_START: Unlocking with TKey + * @UNS_TKEY_WAIT_REMOVE: Waiting for TKey to be removed (after wrong passphrase) + * @UNS_TKEY_WAIT_INSERT: Waiting for TKey to be inserted (after removal) + * @UNS_TKEY_INSERTED: TKey inserted, starting app load + * @UNS_TKEY_LOADING: Loading TKey app + * @UNS_TKEY_READY: TKey key derived, ready to unlock + * @UNS_TKEY_UNLOCK: Unlocking LUKS partition + * @UNS_UNLOCK_RESULT: Processing unlock result (success or failure) + * @UNS_BAD_PASS: Unlock failed, showing error message + * @UNS_OK: Unlock succeeded, showing success message + */ +enum unlock_state { + UNS_IDLE, + UNS_WAITING_PASS, + UNS_UNLOCK_NORMAL, + UNS_TKEY_START, + UNS_TKEY_WAIT_REMOVE, + UNS_TKEY_WAIT_INSERT, + UNS_TKEY_INSERTED, + UNS_TKEY_LOADING, + UNS_TKEY_READY, + UNS_TKEY_UNLOCK, + UNS_UNLOCK_RESULT, + UNS_BAD_PASS, + UNS_OK, +}; + /** * struct logic_priv - Information maintained by the boot logic as it works * @@ -27,6 +61,7 @@ struct udevice; * @opt_autoboot: true to autoboot the default OS after a timeout * @opt_measure: true to measure loaded images, etc. * @opt_slow_refresh: refresh the UI only when needed + * @opt_tkey: true to use TKey for unlocking encrypted volumes * * @state_loaded: true if the state information has been loaded * @scanning: true if scanning for new OSes @@ -40,6 +75,14 @@ struct udevice; * @selected_seq: sequence number of OS waiting for passphrase, or -1 if none * @ready_to_boot: true if success message shown, ready to boot on next poll * + * @tkey: TKey device (pointer never changes once set) + * @tkey_present: true if TKey is physically present and accessible + * @tkey_load_ctx: TKey app loading context for iterative loading + * @tkey_disk_key: Buffer to store derived disk key from TKey + * @ustate: Current state of the disk unlock process + * @unlock_result: Result of disk unlock (0 = OK, -ve on error) + * @time_error: monotonic time when error message display started + * * @iter: oslist iterator, used to find new OSes * @meas: TPM-measurement device * @oslist: provides OSes to boot; we iterate through each osinfo driver to find @@ -57,6 +100,7 @@ struct logic_priv { bool opt_autoboot; bool opt_measure; bool opt_slow_refresh; + bool opt_tkey; bool state_loaded; bool state_saved; @@ -71,6 +115,14 @@ struct logic_priv { int selected_seq; bool ready_to_boot; + struct udevice *tkey; + bool tkey_present; + struct tkey_load_ctx tkey_load_ctx; + u8 tkey_disk_key[TKEY_DISK_KEY_SIZE]; + enum unlock_state ustate; + int unlock_result; + ulong time_error; + struct oslist_iter iter; struct udevice *meas; struct udevice *oslist; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The bootctl headers should before the test headers. Fix this. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/boot/bootctl/bootctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/boot/bootctl/bootctl.c b/test/boot/bootctl/bootctl.c index ff1a9722fe3..6c12cf708e0 100644 --- a/test/boot/bootctl/bootctl.c +++ b/test/boot/bootctl/bootctl.c @@ -17,8 +17,6 @@ #include <dm.h> #include <expo.h> #include <os.h> -#include <test/ut.h> -#include <test/video.h> #include "bootctl_common.h" #include <bootctl/logic.h> #include <bootctl/measure.h> @@ -26,6 +24,8 @@ #include <bootctl/state.h> #include <bootctl/ui.h> #include <dm/lists.h> +#include <test/ut.h> +#include <test/video.h> #include "../bootstd_common.h" /* test that expected devices are available and can be probed */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> The position of the USS in the load-app header is incorrect. Fix it in the driver and the emulator, so it matches the tkey-sign program. Co-developed-by: Claude <claude@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/misc/tkey-uclass.c | 16 ++++++++-------- drivers/misc/tkey_emul.c | 9 +++++++-- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/misc/tkey-uclass.c b/drivers/misc/tkey-uclass.c index fad5ffd6534..c0c3bd5a6d3 100644 --- a/drivers/misc/tkey-uclass.c +++ b/drivers/misc/tkey-uclass.c @@ -470,12 +470,12 @@ static int tkey_load_app_header(struct udevice *dev, int app_size, return ret; } - /* USS present flag */ - cmd_frame.data[5] = 1; - /* Copy USS hash (32 bytes) */ - memcpy(&cmd_frame.data[6], uss_hash, 32); + log_debug("USS hash: %*ph\n", 32, uss_hash); + + /* Copy USS hash (32 bytes) starting at data[5] */ + memcpy(&cmd_frame.data[5], uss_hash, 32); /* Pad remaining bytes with zeros */ - memset(&cmd_frame.data[38], '\0', 128 - 38); + memset(&cmd_frame.data[37], '\0', 128 - 37); log_debug("USS hash included in app header\n"); } else { @@ -657,9 +657,9 @@ int tkey_get_pubkey(struct udevice *dev, void *pubkey) return -EIO; } - /* Extract public key (32 bytes) from response */ - if (ret >= TKEY_FRAME_HEADER_SIZE + TKEY_PUBKEY_SIZE) { - memcpy(pubkey, rsp_frame.data, TKEY_PUBKEY_SIZE); + /* Extract public key (32 bytes) from response, skip response code byte */ + if (ret >= TKEY_FRAME_HEADER_SIZE + 1 + TKEY_PUBKEY_SIZE) { + memcpy(pubkey, rsp_frame.data + 1, TKEY_PUBKEY_SIZE); log_debug("Public key retrieved successfully\n"); return 0; } diff --git a/drivers/misc/tkey_emul.c b/drivers/misc/tkey_emul.c index 403e6e819b9..f67e28bd071 100644 --- a/drivers/misc/tkey_emul.c +++ b/drivers/misc/tkey_emul.c @@ -182,8 +182,13 @@ static int handle_firmware_cmd(struct udevice *dev, u8 cmd, const u8 *data) static int handle_app_get_pubkey(struct tkey_emul_priv *priv) { - memcpy(priv->resp, priv->pubkey, 32); - priv->resp_len = 32; + /* + * Response format: 1-byte response code (0x02) + 32-byte pubkey + * tkey_get_pubkey() expects this format and skips the response code + */ + priv->resp[0] = 0x02; /* Response code for GET_PUBKEY */ + memcpy(priv->resp + 1, priv->pubkey, 32); + priv->resp_len = 33; log_debug("GET_PUBKEY\n"); return 0; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Export the function which sets the current TKey so that the luks command can work with 'tkey connect'. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/tkey.c | 2 +- include/tkey.h | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/tkey.c b/cmd/tkey.c index 3a31712990a..a1a382bac0c 100644 --- a/cmd/tkey.c +++ b/cmd/tkey.c @@ -22,7 +22,7 @@ /* Static device pointer set by tkey connect command */ static struct udevice *tkey_dev; -static struct udevice *tkey_get_device(void) +struct udevice *tkey_get_device(void) { struct udevice *dev; int ret; diff --git a/include/tkey.h b/include/tkey.h index e610d91140c..418b6aca7d2 100644 --- a/include/tkey.h +++ b/include/tkey.h @@ -327,4 +327,11 @@ int tkey_emul_set_app_mode_for_test(struct udevice *dev, bool app_mode); */ int tkey_emul_set_connected_for_test(struct udevice *dev, bool connected); +/** + * tkey_get_device() - Get the current TKey device + * + * Return: Pointer to TKey device, or NULL if not found + */ +struct udevice *tkey_get_device(void); + #endif /* _TKEY_UCLASS_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a -p flag to the luks unlock command that allows passing a hex-encoded pre-derived master key, skipping the KDF step. This is useful when the master key has been derived externally, such as from a hardware security module. Adjust the normal flow (without -p) to use a key derived on the TKey output. While that works OK with LUKS1, the 32-byte value is not long enough to work with LUKS2. Update the documentation to describe the new flag. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/luks.c | 42 +++++++++++++++++++++++++++++++----------- doc/usage/cmd/luks.rst | 18 ++++++++++++++++-- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index ec4d400b44e..31805ffa5ad 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -85,10 +85,10 @@ static int unlock_with_tkey(struct blk_desc *dev_desc, printf("Using TKey for disk encryption key\n"); /* Find TKey device */ - ret = uclass_first_device_err(UCLASS_TKEY, &tkey_dev); - if (ret) { - printf("Failed to find TKey device (err %dE)\n", ret); - return ret; + tkey_dev = tkey_get_device(); + if (!tkey_dev) { + printf("Failed to find TKey device\n"); + return -ENOENT; } /* Derive disk key using TKey with passphrase as USS */ @@ -113,7 +113,7 @@ static int unlock_with_tkey(struct blk_desc *dev_desc, TKEY_DISK_KEY_SIZE, false); ret = luks_unlock(dev_desc->bdev, info, tkey_disk_key, - TKEY_DISK_KEY_SIZE, true, master_key, key_size); + TKEY_DISK_KEY_SIZE, false, master_key, key_size); /* Wipe TKey disk key */ memset(tkey_disk_key, '\0', sizeof(tkey_disk_key)); @@ -129,14 +129,21 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, struct udevice *blkmap_dev; const char *passphrase = NULL; bool use_tkey = false; + bool pre_derived = false; int part, ret, version; u8 master_key[128]; char label[64]; u32 key_size; - /* Check for -t flag */ - if (!strcmp(argv[1], "-t")) { - use_tkey = true; + /* Check for flags */ + while (argc > 1 && argv[1][0] == '-') { + if (!strcmp(argv[1], "-t")) { + use_tkey = true; + } else if (!strcmp(argv[1], "-p")) { + pre_derived = true; + } else { + return CMD_RET_USAGE; + } argc--; argv++; } @@ -165,9 +172,21 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, if (use_tkey) { ret = unlock_with_tkey(dev_desc, &info, passphrase, master_key, &key_size); + } else if (pre_derived) { + /* Pre-derived key: passphrase is hex-encoded master key */ + u8 key_buf[64]; + size_t key_len = strlen(passphrase) / 2; + + if (key_len > sizeof(key_buf) || hex2bin(key_buf, passphrase, + key_len)) { + printf("Invalid hex key\n"); + return CMD_RET_FAILURE; + } + ret = luks_unlock(dev_desc->bdev, &info, key_buf, key_len, + true, master_key, &key_size); } else { /* Unlock with passphrase */ - ret = luks_unlock(dev_desc->bdev, &info,(const u8 *)passphrase, + ret = luks_unlock(dev_desc->bdev, &info, (const u8 *)passphrase, strlen(passphrase), false, master_key, &key_size); } @@ -202,8 +221,9 @@ cleanup: static char luks_help_text[] = "detect <interface> <dev[:part]> - detect if partition is LUKS encrypted\n" "luks info <interface> <dev[:part]> - show LUKS header information\n" - "luks unlock [-t] <interface> <dev[:part]> <passphrase> - unlock LUKS partition\n" - " -t: Use TKey hardware security token with passphrase as USS\n"; + "luks unlock [-t] [-p] <interface> <dev[:part]> <passphrase> - unlock LUKS partition\n" + " -t: Use TKey hardware security token with passphrase as USS\n" + " -p: Treat passphrase as hex-encoded pre-derived master key (skip KDF)\n"; U_BOOT_CMD_WITH_SUBCMDS(luks, "LUKS (Linux Unified Key Setup) operations", luks_help_text, diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index ccf915f5844..1a9cba875ce 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -13,7 +13,7 @@ Synopsis luks detect <interface> <dev[:part]> luks info <interface> <dev[:part]> - luks unlock [-t] <interface> <dev[:part]> <passphrase> + luks unlock [-t] [-p] <interface> <dev[:part]> <passphrase> Description ----------- @@ -97,7 +97,8 @@ This command: - **Without -t**: Uses PBKDF2 or Argon2id with the provided passphrase - **With -t**: Uses TKey hardware token with passphrase as USS (User-Supplied - Secret) to derive a disk encryption key + Secret) to derive a disk encryption key. You can use 'tkey connect' to + select which TKey to use, otherwise it uses the first one it finds. 4. Attempts to unlock each active key slot 5. Verifies the master key against the stored digest @@ -128,6 +129,12 @@ be used to access files on the unlocked partition. passphrase is used as the USS (User-Supplied Secret) to derive a disk encryption key from the TKey's public key. +-p + Optional flag to treat the passphrase as a hex-encoded pre-derived master + key, skipping the KDF (Key Derivation Function) step. This is useful when + the master key has already been derived externally, such as from a hardware + security module or other key management system. + interface The storage interface type (e.g., mmc, usb, scsi) @@ -250,6 +257,13 @@ Unlock using TKey hardware token:: Successfully unlocked with key slot 0! Unlocked LUKS partition as blkmap device 'luks-mmc-0:2' +Unlock using a pre-derived master key (hex-encoded):: + + => luks unlock -p mmc 0:2 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef + Unlocking LUKS2 partition... + Successfully unlocked with key slot 0! + Unlocked LUKS partition as blkmap device 'luks-mmc-0:2' + Configuration ------------- -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Switch away from a pre-derived key so that LUKS2 partitions can be unlocked. Update the ulock message to be more generic. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- boot/bootctl/logic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boot/bootctl/logic.c b/boot/bootctl/logic.c index 96cc270ae2c..2ed106628e7 100644 --- a/boot/bootctl/logic.c +++ b/boot/bootctl/logic.c @@ -334,9 +334,9 @@ static int perform_tkey_unlock(struct udevice *dev, struct osinfo *os, int seq, * processed by PBKDF2/Argon2 just like a text passphrase would be. * This matches how cryptsetup --key-file works. */ - log_info("Using LUKS1 unlock with binary passphrase\n"); + log_info("Using LUKS unlock with binary passphrase\n"); ret = luks_unlock(os->bflow.blk, &pinfo, priv->tkey_disk_key, - TKEY_DISK_KEY_SIZE, true, master_key, key_sizep); + TKEY_DISK_KEY_SIZE, false, master_key, key_sizep); if (ret) return log_msg_ret("htu", ret); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update the bootctl settings to use a TKey with sandbox. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/bootctl.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/bootctl.dtsi b/include/bootctl.dtsi index 4070fa32c5f..36e2be04be9 100644 --- a/include/bootctl.dtsi +++ b/include/bootctl.dtsi @@ -31,6 +31,11 @@ measure; #endif + /* use TKey for unlocking encrypted volumes */ +#ifdef CONFIG_SANDBOX + tkey; +#endif + /* restrict labels to boot (separated by space) */ #ifdef CONFIG_QEMU labels = "virtio"; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Enable the bootctl tests (only for the sandbox board) so we can keep this functionality working. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- configs/sandbox_defconfig | 1 + test/Kconfig | 1 - test/boot/Makefile | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 55f8ddcc952..44185ccb7eb 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -371,5 +371,6 @@ CONFIG_ARGON2=y CONFIG_BACKTRACE=y CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y +CONFIG_UT_BOOTCTL=y CONFIG_UT_TIME=y CONFIG_UT_DM=y diff --git a/test/Kconfig b/test/Kconfig index 27847250132..96723940bac 100644 --- a/test/Kconfig +++ b/test/Kconfig @@ -77,7 +77,6 @@ endif # UT_LIB config UT_BOOTCTL bool "Unit tests for boot schema" depends on BOOTSTD && SANDBOX - default y config UT_BOOTSTD bool "Unit tests for standard boot" diff --git a/test/boot/Makefile b/test/boot/Makefile index 70e15bf63fa..21f533cdc4c 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_BLK_LUKS) += luks.o obj-$(CONFIG_EXPO) += expo.o expo_common.o obj-$(CONFIG_CEDIT) += cedit.o expo_common.o +obj-$(CONFIG_UT_BOOTCTL) += bootctl/ endif ifdef CONFIG_SANDBOX -- 2.43.0
participants (1)
-
Simon Glass