-
-
Notifications
You must be signed in to change notification settings - Fork 211
Expand file tree
/
Copy pathgui-init.sh
More file actions
executable file
·1161 lines (1060 loc) · 39.7 KB
/
Copy pathgui-init.sh
File metadata and controls
executable file
·1161 lines (1060 loc) · 39.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# Boot from a local disk installation
BOARD_NAME=${CONFIG_BOARD_NAME:-${CONFIG_BOARD}}
MAIN_MENU_TITLE="${BOARD_NAME} | $CONFIG_BRAND_NAME Boot Menu"
export BG_COLOR_MAIN_MENU="normal"
. /etc/functions.sh
. /etc/gui_functions.sh
. /etc/gpg_functions.sh
. /etc/luks-functions.sh
. /tmp/config
# Detect the terminal this gui-init session is running on. The user
# interacting with gui-init (via whiptail) is the source of truth for the
# "active" terminal — prompts, GPG/pinentry, and input all go to/from there.
# $(tty) works here because cttyhack (exec'd by /init) has already replaced
# fd0/1/2 with the correct console device before launching this script.
# Fall back to /sys/class/tty/console/active (last entry = preferred console,
# same source used by systemd and busybox cttyhack) when tty is unavailable.
detect_heads_tty
# skip_to_menu is set if the user selects "continue to the main menu" from any
# error, so we will indeed go to the main menu even if other errors occur. It's
# reset when we reach the main menu so the user can retry from the main menu and
# # see errors again.
skip_to_menu="false"
INTEGRITY_GATE_REQUIRED="n"
mount_boot() {
TRACE_FUNC
# Mount local disk if it is not already mounted
while ! grep -q /boot /proc/mounts; do
# try to mount if CONFIG_BOOT_DEV exists
if [ -e "$CONFIG_BOOT_DEV" ]; then
if mount -o ro "$CONFIG_BOOT_DEV" /boot; then
continue
fi
fi
# CONFIG_BOOT_DEV doesn't exist or couldn't be mounted, so give user options.
# LUKS_PARTITION_DETECTED is set by detect_boot_device (via mount_possible_boot_device)
# when it skips a LUKS partition -- reuse that result to distinguish
# "OS installed without separate /boot" from "no OS found at all".
BG_COLOR_MAIN_MENU="error"
local boot_msg
if [ "${LUKS_PARTITION_DETECTED:-n}" = "y" ]; then
boot_msg="An encrypted OS was detected but no separate /boot partition was found.\n\n$CONFIG_BRAND_NAME requires a separate, unencrypted /boot partition.\n\nMost OS installers do not create this layout by default. Only DVD/live\nISOs that detect legacy boot (BIOS/CSM mode) will offer the correct\npartition scheme. Use 'Boot from USB' to boot a live ISO and reinstall\nyour OS with a separate /boot partition.\n\nHow would you like to proceed?"
else
boot_msg="No bootable OS was found on any disk.\n\n$CONFIG_BRAND_NAME requires a separate, unencrypted /boot partition\ncontaining grub configuration files.\n\nIf you are installing an OS for the first time, use 'Boot from USB' to\nboot a live ISO. Only DVD/live ISOs that detect legacy boot (BIOS/CSM)\nwill offer the correct partition scheme with a separate /boot.\n\nHow would you like to proceed?"
fi
whiptail_error --title "ERROR: No /boot Partition Found" \
--menu "$boot_msg" 0 80 4 \
'u' ' Boot from USB' \
'b' ' Select a new boot device' \
'm' ' Continue to the main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
u)
exec /bin/usb-init.sh
;;
b)
if config-gui.sh boot_device_select; then
# update CONFIG_BOOT_DEV
# shellcheck source=/dev/null
. /tmp/config
BG_COLOR_MAIN_MENU="normal"
fi
;;
m)
skip_to_menu="true"
break
;;
*)
recovery "User requested recovery shell"
;;
esac
done
}
verify_global_hashes() {
TRACE_FUNC
# Check the hashes of all the files, ignoring signatures for now
check_config /boot force
TMP_HASH_FILE="/tmp/kexec/kexec_hashes.txt"
TMP_TREE_FILE="/tmp/kexec/kexec_tree.txt"
TMP_PACKAGE_TRIGGER_PRE="/tmp/kexec/kexec_package_trigger_pre.txt"
TMP_PACKAGE_TRIGGER_POST="/tmp/kexec/kexec_package_trigger_post.txt"
if verify_checksums /boot; then
return 0
elif [[ ! -f "$TMP_HASH_FILE" || ! -f "$TMP_TREE_FILE" ]]; then
if (whiptail_error --title 'ERROR: Missing File!' \
--yesno "One of the files containing integrity information for /boot is missing!\n\nIf you are setting up heads for the first time or upgrading from an older version, select Yes to create the missing files.\n\nOtherwise this could indicate a compromise and you should select No to return to the main menu.\n\nWould you like to create the missing files now?" 0 80); then
if update_checksums; then
BG_COLOR_MAIN_MENU="normal"
return 0
else
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
fi
BG_COLOR_MAIN_MENU="error"
return 1
else
CHANGED_FILES=$(grep -v 'OK$' /tmp/hash_output | cut -f1 -d ':' | tee -a /tmp/hash_output_mismatches)
CHANGED_FILES_COUNT=$(wc -l /tmp/hash_output_mismatches | cut -f1 -d ' ')
# if files changed before package manager started, show stern warning
if [ -f "$TMP_PACKAGE_TRIGGER_PRE" ]; then
PRE_CHANGED_FILES=$(grep '^CHANGED_FILES' "$TMP_PACKAGE_TRIGGER_POST" | cut -f 2 -d '=' | tr -d '"')
TEXT="The following files failed the verification process BEFORE package updates ran:\n${PRE_CHANGED_FILES}\n\nCompare against the files $CONFIG_BRAND_NAME has detected have changed:\n${CHANGED_FILES}\n\nThis could indicate a compromise!\n\nWould you like to update your checksums anyway?"
# if files changed after package manager started, probably caused by package manager
elif [ -f "$TMP_PACKAGE_TRIGGER_POST" ]; then
LAST_PACKAGE_LIST=$(grep -E "^(Install|Remove|Upgrade|Reinstall):" "$TMP_PACKAGE_TRIGGER_POST")
UPDATE_INITRAMFS_PACKAGE=$(grep '^UPDATE_INITRAMFS_PACKAGE' "$TMP_PACKAGE_TRIGGER_POST" | cut -f 2 -d '=' | tr -d '"')
if [ "$UPDATE_INITRAMFS_PACKAGE" != "" ]; then
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis is likely due to package triggers in$UPDATE_INITRAMFS_PACKAGE.\n\nYou will need to update your checksums for all files in /boot.\n\nWould you like to update your checksums now?"
else
TEXT="The following files failed the verification process AFTER package updates ran:\n${CHANGED_FILES}\n\nThis might be due to the following package updates:\n$LAST_PACKAGE_LIST.\n\nYou will need to update your checksums for all files in /boot.\n\nWould you like to update your checksums now?"
fi
else
if [ $CHANGED_FILES_COUNT -gt 10 ]; then
# drop to console to show full file list
whiptail_error --title 'ERROR: Boot Hash Mismatch' \
--msgbox "${CHANGED_FILES_COUNT} files failed the verification process!\\n\nThis could indicate a compromise!\n\nHit OK to review the list of files.\n\nType \"q\" to exit the list and return." 0 80
echo "Type \"q\" to exit the list and return." >>/tmp/hash_output_mismatches
less /tmp/hash_output_mismatches
#move outdated hash mismatch list
mv /tmp/hash_output_mismatches /tmp/hash_output_mismatch_old
TEXT="${CHANGED_FILES_COUNT} files failed the verification process.\n\nThis could indicate a compromise!\n\nWould you like to investigate discrepancies or update your checksums now?"
else
TEXT="The following files failed the verification process:\n\n${CHANGED_FILES}\n\nThis could indicate a compromise!\n\nWould you like to investigate discrepancies or update your checksums now?"
fi
fi
local menu_text
menu_text="$TEXT"
while true; do
TRACE_FUNC
whiptail_error --title 'ERROR: Boot Hash Mismatch' \
--menu "$menu_text\n\nChoose an action:" 0 80 3 \
'i' ' Investigate discrepancies -->' \
'u' ' Update checksums now' \
'm' ' Return to main menu' \
2>/tmp/whiptail || {
BG_COLOR_MAIN_MENU="error"
return 1
}
option=$(cat /tmp/whiptail)
case "$option" in
i)
investigate_integrity_discrepancies
;;
u)
if update_checksums; then
BG_COLOR_MAIN_MENU="normal"
return 0
else
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
;;
m | *)
BG_COLOR_MAIN_MENU="error"
return 1
;;
esac
done
fi
}
prompt_update_checksums() {
TRACE_FUNC
# Signing /boot with -r increments the TPM rollback counter. If the counter
# is broken or absent (tpm_reset_required), the increment will fail and DIE.
# The user must reset the TPM first; that flow re-creates the counter.
if [ "$CONFIG_TPM" = "y" ] && tpm_reset_required; then
whiptail_error --title 'TPM Reset Required' \
--msgbox "Cannot sign /boot: TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM), then update checksums." 0 80
return 1
fi
if (whiptail_warning --title 'Update Checksums and sign all files in /boot' \
--yesno "You have chosen to update the checksums and sign all of the files in /boot.\n\nThis means that you trust that these files have not been tampered with.\n\nYou will need your GPG key available, and this change will modify your disk.\n\nDo you want to continue?" 0 80); then
if update_checksums; then
return 0
fi
# update_checksums may have set the TPM-reset-required marker
# during its execution (e.g. check_tpm_counter hit "out of
# resources"). Show the targeted TPM message instead of the
# generic failure so the user knows exactly what to do.
if tpm_reset_required; then
whiptail_error --title 'TPM Reset Required' \
--msgbox "Cannot sign /boot: TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM), then update checksums." 0 80
else
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
fi
return 1
fi
return 1
}
gate_reseal_with_integrity_report() {
TRACE_FUNC
local token_ok="y"
if tpm_reset_required; then
debug_tpm_reset_required_state
whiptail_error --title 'ERROR: TPM Reset Required' \
--msgbox "TPM state is inconsistent for sealing/unsealing operations.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM)." 0 80
return 1
fi
if [ "$INTEGRITY_GATE_REQUIRED" != "y" ]; then
DEBUG "Skipping integrity gate: no TOTP/HOTP failure context"
return 0
fi
INTEGRITY_REPORT_HASH_STATE="UNKNOWN"
STATUS "Running integrity report before resealing secrets"
report_integrity_measurements
local report_rc=$?
DEBUG "gate_reseal_with_integrity_report: report_integrity_measurements rc=$report_rc"
DEBUG "gate_reseal_with_integrity_report: INTEGRITY_REPORT_HASH_STATE=$INTEGRITY_REPORT_HASH_STATE"
if [ "$INTEGRITY_REPORT_HASH_STATE" != "OK" ]; then
DEBUG "returned from integrity report, now running investigation"
STATUS "Investigating integrity discrepancies before resealing"
if ! investigate_integrity_discrepancies; then
DEBUG "investigation indicated problem, aborting gate"
WARN "Integrity investigation did not clear discrepancies; reseal action aborted"
return 1
fi
DEBUG "gate_reseal_with_integrity_report: about to verify detached signature"
STATUS "Verifying /boot detached signature before resealing"
DEBUG "ls -l /boot/kexec.sig: $(ls -l /boot/kexec.sig 2>/dev/null || echo missing)"
if ! detached_kexec_signature_valid /boot; then
DEBUG "detached_kexec_signature_valid failed"
WARN "Detached signature verification failed; refusing reseal action"
local sig_fail_msg
sig_fail_msg="Cannot proceed with sealing new secrets because /boot/kexec.sig could not be verified with your current keyring.\n\nTreat /boot as untrusted and recover ownership first."
whiptail_error --title 'ERROR: Signature Verification Failed' \
--msgbox "$sig_fail_msg" 0 80
return 1
fi
STATUS_OK "Integrity checks passed for reseal prerequisites"
else
DEBUG "gate_reseal_with_integrity_report: integrity is OK, skipping investigation and detached signature verification"
STATUS_OK "Integrity checks passed for reseal prerequisites"
fi
if [ -x /bin/hotp_verification ]; then
token_ok="n"
while [ "$token_ok" != "y" ]; do
enable_usb
# wait_for_gpg_card already called release_scdaemon on success,
# starting the NK3 CCID teardown. This safety call covers the
# case where scdaemon was restarted between then and now.
release_scdaemon
DEBUG "gate_reseal_with_integrity_report: checking HOTP token presence"
STATUS "Checking $DONGLE_BRAND presence before sealing"
if hotp_verification info >/dev/null 2>&1; then
STATUS_OK "$DONGLE_BRAND present and accessible"
token_ok="y"
break
fi
DEBUG "gate_reseal_with_integrity_report: HOTP token not accessible"
if ! whiptail_warning --title "$DONGLE_BRAND Required" \
--yes-button "Retry" --no-button "Abort" \
--yesno "Your $DONGLE_BRAND must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
return 1
fi
done
fi
if ! whiptail_warning --title 'Integrity Gate Passed' \
--yesno "Integrity checks completed.\n\nProceed with TOTP/HOTP reseal action?" 0 80; then
return 1
fi
INTEGRITY_GATE_REQUIRED="n"
return 0
}
generate_totp_hotp() {
TRACE_FUNC
tpm_owner_passphrase="$1" # May be empty, will prompt if needed and empty
if [ "$CONFIG_TPM" = "y" ] && tpm_reset_required; then
debug_tpm_reset_required_state
whiptail_error --title 'ERROR: TPM Reset Required' \
--msgbox "Cannot generate a new TPM-backed TOTP/HOTP secret while TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM)." 0 80
return 1
fi
if [ "$CONFIG_TPM" != "y" ] && [ -x /bin/hotp_verification ]; then
# If we don't have a TPM, but we have a HOTP USB Security dongle
TRACE_FUNC
/bin/seal-hotpkey.sh ||
DIE "Failed to generate HOTP secret"
elif /bin/seal-totp.sh "$BOARD_NAME" "$tpm_owner_passphrase"; then
if [ -x /bin/hotp_verification ]; then
# If we have a TPM and a HOTP USB Security dongle
if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then
INPUT "Once you have scanned the QR code, press Enter to configure your $DONGLE_BRAND"
fi
TRACE_FUNC
/bin/seal-hotpkey.sh || DIE "Failed to generate HOTP secret"
else
if [ "$CONFIG_TOTP_SKIP_QRCODE" != y ]; then
INPUT "Once you have scanned the QR code, press Enter to continue"
fi
fi
clear
else
# seal-totp.sh already printed an explanatory error (e.g. missing
# primary handle) and guided the user to reset the TPM. Don't add
# confusing generic warnings here, just propagate failure.
return 1
fi
}
prompt_missing_gpg_key_action() {
TRACE_FUNC
local retry_label retry_msg
if [ "$CONFIG_HAVE_GPG_KEY_BACKUP" = "y" ]; then
retry_label=" Retry (insert $DONGLE_BRAND or backup USB drive)"
retry_msg="Cannot sign /boot because no private GPG signing key is available ($DONGLE_BRAND not inserted, wiped, or key not set up).\n\nInsert your $DONGLE_BRAND or backup USB drive and retry.\n\nHow would you like to proceed?"
else
retry_label=" Retry (after connecting $DONGLE_BRAND)"
retry_msg="Cannot sign /boot because no private GPG signing key is available ($DONGLE_BRAND not inserted, wiped, or key not set up).\n\nInsert your $DONGLE_BRAND and retry.\n\nHow would you like to proceed?"
fi
whiptail_error --title "ERROR: GPG signing key unavailable" \
--menu "$retry_msg" 0 80 4 \
'r' "$retry_label" \
'F' ' OEM Factory Reset / Re-Ownership' \
'm' ' Return to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
r)
return 0
;;
F)
oem-factory-reset.sh
;;
x)
recovery "User requested recovery shell"
;;
m | *)
return 1
;;
esac
}
update_totp() {
TRACE_FUNC
# update the TOTP code
date=$(date "+%Y-%m-%d %H:%M:%S %Z")
tries=0
if [ "$CONFIG_TPM" != "y" ]; then
TOTP="NO TPM"
else
TOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-totp.sh)
if [ $? -ne 0 ]; then
local totp_menu_text
INTEGRITY_GATE_REQUIRED="y"
BG_COLOR_MAIN_MENU="error"
if [ "$skip_to_menu" = "true" ]; then
return 1 # Already asked to skip to menu from a prior error
fi
DEBUG "TPM state at TOTP failure:"
DEBUG "$(pcrs)"
if [ -f /tmp/secret/tpm_da_lockout ]; then
rm -f /tmp/secret/tpm_da_lockout
da_lockout_msg=""
if [ -f /tmp/secret/tpm_da_lockout_msg ]; then
da_lockout_msg=$(cat /tmp/secret/tpm_da_lockout_msg)
rm -f /tmp/secret/tpm_da_lockout_msg
fi
totp_menu_text=$(
cat <<EOF
ERROR: TPM dictionary attack lockout prevented TOTP unseal.
Repeat bad TPM authentication attempts, typically from use of
incorrect TPM owner passphrase during the current session,
have triggered the TPM's dictionary attack defense mechanism.
${da_lockout_msg:+TPM reports: $da_lockout_msg
}TPM 1.2 lockout follows the TCG standard exponential backoff:
early failures unlock in seconds to minutes,
higher failure counts may take hours.
The counter resets 24h after the last failure.
To recover:
- Power off and wait for the lockout to expire.
- Then reboot and the TPM will accept auth attempts again.
- If the issue persists, reset the TPM from the menu below.
How would you like to proceed?
EOF
)
whiptail_error --title "ERROR: TPM Dictionary Attack Lockout" \
--menu "$totp_menu_text" 0 80 4 \
'p' ' Reset the TPM' \
'i' ' Ignore error and continue to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
else
totp_menu_text=$(
cat <<EOF
ERROR: $CONFIG_BRAND_NAME couldn't generate the TOTP code.
After OEM Factory Reset / Re-Ownership, this is expected on first boot
until you generate a new HOTP/TOTP secret.
If you have just completed a factory reset, or just reflashed your BIOS,
you should generate a new HOTP/TOTP secret.
If this is the first time the system has booted, you should reset the TPM
and set your own passphrase.
If you have not just reflashed your BIOS, THIS COULD INDICATE TAMPERING!
How would you like to proceed?
EOF
)
whiptail_error --title "ERROR: TOTP Generation Failed!" \
--menu "$totp_menu_text" 0 80 4 \
'g' ' Generate new HOTP/TOTP secret' \
'p' ' Reset the TPM' \
'i' ' Ignore error and continue to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
fi
option=$(cat /tmp/whiptail)
case "$option" in
g)
if tpm_reset_required; then
debug_tpm_reset_required_state
whiptail_error --title 'ERROR: TPM Reset Required' \
--msgbox "Cannot generate a new TPM-backed TOTP/HOTP secret while TPM state is inconsistent.\n\nReset the TPM first (Options -> TPM/TOTP/HOTP Options -> Reset the TPM)." 0 80
return 1
elif gate_reseal_with_integrity_report && (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80); then
if generate_totp_hotp; then
update_totp || true
BG_COLOR_MAIN_MENU="normal"
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
fi
;;
i)
skip_to_menu="true"
return 1
;;
# "Reset the TPM" from the TOTP failure whiptail menu.
# Show the integrity report so the user can see the state,
# but do not force the investigation / signing path —
# that would attempt TPM counter operations requiring the
# current owner password, which is unknown (that is why
# we are resetting). reset_tpm() handles everything:
# new password, counter create, /boot signing, TOTP/HOTP
# generation, DUK reseal, and reboot.
p)
report_integrity_measurements
if reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
;;
x)
recovery "User requested recovery shell"
;;
esac
else
INTEGRITY_GATE_REQUIRED="n"
fi
fi
}
update_hotp() {
TRACE_FUNC
HOTP="Unverified"
if [ ! -x /bin/hotp_verification ]; then
HOTP='N/A'
return
fi
local hotp_token_info hotp_exit attempt
# Ensure dongle is present; capture info for PIN counter display
STATUS "Checking $DONGLE_BRAND presence"
if ! hotp_token_info="$(hotp_verification info)"; then
if [ "$skip_to_menu" = "true" ]; then
return 1 # Already asked to skip to menu from a prior error
fi
if ! whiptail_warning \
--title "WARNING: Please Insert Your $DONGLE_BRAND" \
--yes-button "Retry" --no-button "Skip" \
--yesno "Your $DONGLE_BRAND was not detected.\n\nPlease insert your $DONGLE_BRAND" 0 80; then
HOTP="Error checking code, Insert $DONGLE_BRAND and retry"
BG_COLOR_MAIN_MENU="warning"
return
fi
if ! hotp_token_info="$(hotp_verification info)"; then
HOTP="Error checking code, Insert $DONGLE_BRAND and retry"
BG_COLOR_MAIN_MENU="warning"
return
fi
fi
# Show dongle firmware version with color coding so users know when to upgrade
hotpkey_fw_display "$hotp_token_info" "$DONGLE_BRAND"
# Unseal HOTP secret from TPM once; if this fails don't proceed at all
HOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-hotp.sh)
if [ -z "$HOTP" ]; then
WARN "Unable to unseal HOTP secret from TPM"
HOTP="Error checking code, Insert $DONGLE_BRAND and retry"
BG_COLOR_MAIN_MENU="warning"
return
fi
# Try HOTP check up to 3 times.
# Retries handle transient USB/timing failures; a definitive code mismatch
# (exit 4 or 7) breaks immediately since the same code won't verify again.
# PIN retry count is shown only before a retry so normal boots stay silent.
for attempt in 1 2 3; do
# Don't output HOTP codes to screen, so as to make replay attacks harder
STATUS "Verifying HOTP code"
hotp_verification check "$HOTP"
hotp_exit=$?
case "$hotp_exit" in
0)
HOTP="Success"
BG_COLOR_MAIN_MENU="normal"
STATUS_OK "HOTP code verified"
return
;;
4 | 7) # 4: code incorrect, 7: not a valid HOTP code — no point retrying same code
HOTP="Invalid code"
BG_COLOR_MAIN_MENU="error"
break
;;
6) # EXIT_SLOT_NOT_PROGRAMMED — sealing was never completed or failed mid-way
HOTP="HOTP slot not configured"
BG_COLOR_MAIN_MENU="warning"
break
;;
*)
# Transient error (USB glitch etc.) — retry if attempts remain
if [ "$attempt" -lt 3 ]; then
WARN "HOTP check failed (attempt $attempt/3), retrying"
else
HOTP="Error checking code, Insert $DONGLE_BRAND and retry"
BG_COLOR_MAIN_MENU="warning"
fi
;;
esac
done
if [[ "$HOTP" = "HOTP slot not configured" ]]; then
WARN "$DONGLE_BRAND HOTP slot is not configured"
STATUS "Verify TOTP against your phone to confirm TPM is intact, then press Escape to continue"
show_totp_until_esc
whiptail_warning --title "HOTP Not Configured" \
--menu "The HOTP slot on your $DONGLE_BRAND is not configured.\n\nThis can happen if HOTP sealing was interrupted (connection error, dongle removed during setup).\n\nPlease generate a new TOTP/HOTP secret to configure it." 0 80 2 \
'g' ' Generate new TOTP/HOTP secret' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
g)
if gate_reseal_with_integrity_report && (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80); then
if generate_totp_hotp; then
update_totp || true
HOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-hotp.sh)
[ -n "$HOTP" ] && hotp_verification check "$HOTP" >/dev/null 2>&1 && HOTP="Success"
BG_COLOR_MAIN_MENU="normal"
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
fi
;;
x)
recovery "User requested recovery shell"
;;
esac
return
elif [[ "$HOTP" = "Invalid code" ]]; then
INTEGRITY_GATE_REQUIRED="y"
STATUS "HOTP failed - verify TOTP against your phone to confirm TPM integrity, then press Escape to continue"
show_totp_until_esc
local hotp_error_msg
hotp_error_msg="ERROR: $CONFIG_BRAND_NAME couldn't validate the HOTP code.\n\nIf you just reflashed your BIOS, you should generate a new TOTP/HOTP secret.\n\nIf you have not just reflashed your BIOS, THIS COULD INDICATE TAMPERING!\n\nHow would you like to proceed?"
whiptail_error --title "ERROR: HOTP Validation Failed!" \
--menu "$hotp_error_msg" 0 80 3 \
'g' ' Generate new TOTP/HOTP secret' \
'i' ' Ignore error and continue to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
g)
if gate_reseal_with_integrity_report && (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80); then
if generate_totp_hotp; then
update_totp || true
HOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-hotp.sh)
[ -n "$HOTP" ] && hotp_verification check "$HOTP" >/dev/null 2>&1 && HOTP="Success"
BG_COLOR_MAIN_MENU="normal"
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
fi
;;
i)
return 1
;;
x)
recovery "User requested recovery shell"
;;
esac
elif [[ "$HOTP" = "Error checking code"* ]]; then
INTEGRITY_GATE_REQUIRED="y"
STATUS "HOTP verification failed after 3 retries - verify TOTP against your phone to confirm TPM integrity, then press Escape to continue"
show_totp_until_esc
whiptail_warning --title "HOTP Verification Failed" \
--menu "The $DONGLE_BRAND could not be verified after multiple attempts.\n\nThis may indicate a USB connection issue or dongle problem.\n\nPlease insert your $DONGLE_BRAND and try again, or verify TOTP to continue." 0 80 2 \
'r' ' Retry HOTP verification' \
'i' ' Ignore and continue to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
r) update_hotp ;;
i) INTEGRITY_GATE_REQUIRED="n" ;;
esac
else
INTEGRITY_GATE_REQUIRED="n"
fi
}
clean_boot_check() {
TRACE_FUNC
# assume /boot mounted
if ! grep -q /boot /proc/mounts; then
return
fi
# check for any kexec files in /boot
kexec_files=$(find /boot -name kexec*.txt)
[ ! -z "$kexec_files" ] && return
#check for GPG key in keyring
GPG_KEY_COUNT=$(gpg -k 2>/dev/null | wc -l)
[ $GPG_KEY_COUNT -ne 0 ] && return
# check for USB security token
if [ -x /bin/hotp_verification ]; then
if ! gpg --card-status >/dev/null; then
return
fi
fi
# OS is installed, no kexec files present, no GPG keys in keyring, security token present
# prompt user to run OEM factory reset
oem-factory-reset.sh \
"Clean Boot Detected - Perform OEM Factory Reset / Re-Ownership?"
}
check_gpg_key() {
TRACE_FUNC
GPG_KEY_COUNT=$(gpg -k 2>/dev/null | wc -l)
if [ $GPG_KEY_COUNT -eq 0 ]; then
BG_COLOR_MAIN_MENU="error"
if [ "$skip_to_menu" = "true" ]; then
return 1 # Already asked to skip to menu from a prior error
fi
local gpg_error_msg
gpg_error_msg="ERROR: $CONFIG_BRAND_NAME couldn't find any GPG keys in your keyring.\n\nIf this is the first time the system has booted, you should add a public GPG key to the BIOS now.\n\nIf you just reflashed a new BIOS, you'll need to add at least one public key to the keyring.\n\nIf you have not just reflashed your BIOS, THIS COULD INDICATE TAMPERING!\n\nHow would you like to proceed?"
whiptail_error --title "ERROR: GPG keyring empty!" \
--menu "$gpg_error_msg" 0 80 4 \
'g' ' Add a GPG key to the running BIOS' \
'F' ' OEM Factory Reset / Re-Ownership' \
'i' ' Ignore error and continue to main menu' \
'x' ' Exit to recovery shell' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
g)
gpg-gui.sh && BG_COLOR_MAIN_MENU="normal"
;;
i)
skip_to_menu="true"
return 1
;;
F)
oem-factory-reset.sh
;;
x)
recovery "User requested recovery shell"
;;
esac
fi
}
prompt_auto_default_boot() {
TRACE_FUNC
if pause_automatic_boot; then
STATUS "Attempting default boot"
attempt_default_boot
fi
}
show_main_menu() {
TRACE_FUNC
date=$(date "+%Y-%m-%d %H:%M:%S %Z")
whiptail_type $BG_COLOR_MAIN_MENU --title "$MAIN_MENU_TITLE" \
--menu "$date\nTOTP: $TOTP | HOTP: $HOTP" 0 80 10 \
'd' ' Default boot' \
'r' ' Refresh TOTP/HOTP' \
'o' ' Options -->' \
's' ' System Info' \
'p' ' Power Off' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
d)
attempt_default_boot
;;
r)
update_totp && update_hotp
;;
o)
show_options_menu
;;
s)
show_system_info
;;
p)
poweroff.sh
;;
esac
}
show_options_menu() {
TRACE_FUNC
whiptail_type $BG_COLOR_MAIN_MENU --title "$CONFIG_BRAND_NAME Options" \
--menu "" 0 80 10 \
'b' ' Boot Options -->' \
't' ' TPM/TOTP/HOTP Options -->' \
'i' ' Investigate integrity discrepancies -->' \
'h' ' Change system time' \
'u' ' Update checksums and sign all files in /boot' \
'c' ' Change configuration settings -->' \
'f' ' Flash/Update the BIOS -->' \
'g' ' GPG Options -->' \
'F' ' OEM Factory Reset / Re-Ownership -->' \
'C' ' Reencrypt LUKS container -->' \
'P' ' Change LUKS Disk Recovery Key passphrase ->' \
'R' ' Check/Update file hashes on root disk -->' \
'x' ' Exit to recovery shell' \
'r' ' <-- Return to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
b)
show_boot_options_menu
;;
t)
show_tpm_totp_hotp_options_menu
;;
i)
investigate_integrity_discrepancies
;;
h)
change-time.sh
;;
u)
prompt_update_checksums
;;
c)
config-gui.sh
;;
f)
flash-gui.sh
;;
g)
gpg-gui.sh
;;
F)
oem-factory-reset.sh
;;
C)
luks_reencrypt
luks_secrets_cleanup
;;
P)
luks_change_passphrase
luks_secrets_cleanup
;;
R)
root-hashes-gui.sh
;;
x)
recovery "User requested recovery shell"
;;
r) ;;
esac
}
show_boot_options_menu() {
TRACE_FUNC
whiptail_type $BG_COLOR_MAIN_MENU --title "Boot Options" \
--menu "Select A Boot Option" 0 80 10 \
'm' ' Show OS boot menu' \
'u' ' USB boot' \
'i' ' Ignore tampering and force a boot (Unsafe!)' \
'r' ' <-- Return to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
m)
# select a kernel from the menu
select_os_boot_option
;;
u)
exec /bin/usb-init.sh
;;
i)
force_unsafe_boot
;;
r) ;;
esac
}
show_tpm_totp_hotp_options_menu() {
TRACE_FUNC
whiptail_type $BG_COLOR_MAIN_MENU --title "TPM/TOTP/HOTP Options" \
--menu "Select An Option" 0 80 10 \
'g' ' Generate new TOTP/HOTP secret' \
'r' ' Reset the TPM' \
't' ' TOTP/HOTP does not match after refresh, troubleshoot' \
'm' ' <-- Return to main menu' \
2>/tmp/whiptail || recovery "GUI menu failed"
option=$(cat /tmp/whiptail)
case "$option" in
g)
if gate_reseal_with_integrity_report && generate_totp_hotp; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
# If reseal did not reboot (no LUKS devices), refresh display so
# the user sees the new TOTP/HOTP state without a manual 'r'
update_totp && update_hotp || true
fi
;;
# "Reset the TPM" from the TPM/TOTP/HOTP options whiptail menu.
# Show the integrity report so the user can see the state,
# but do not force the investigation / signing path —
# that would attempt TPM counter operations requiring the
# current owner password, which is unknown (that is why
# we are resetting). reset_tpm() handles everything:
# new password, counter create, /boot signing, TOTP/HOTP
# generation, DUK reseal, and reboot.
r)
report_integrity_measurements
if reset_tpm; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
;;
t)
prompt_totp_mismatch
;;
m) ;;
esac
}
prompt_totp_mismatch() {
TRACE_FUNC
if (whiptail_warning --title "TOTP/HOTP code mismatched" \
--yesno "TOTP/HOTP code mismatches could indicate TPM tampering or clock drift.\n\nThe current UTC time is: $(date "+%Y-%m-%d %H:%M:%S")\nIf this is incorrect, set the correct time and check TOTP/HOTP again.\n\nDo you want to change the time?" 0 80); then
change-time.sh
fi
}
reset_tpm() {
TRACE_FUNC
if [ "$CONFIG_TPM" = "y" ]; then
if (whiptail_warning --title 'Reset the TPM' \
--yesno "This will clear the TPM and replace its Owner passphrase with a new one!\n\nDo you want to proceed?" 0 80); then
if ! prompt_new_owner_password; then
INPUT "Press Enter to return to the menu..."
return 1
fi
# Verify TPM reset succeeded before proceeding to counter
# creation, signing, TOTP generation, and DUK resealing.
# A failed reset would leave the TPM in an inconsistent state
# (old passphrase with unknown PCRs), causing confusing errors
# downstream. Show the actual error to the user and return
# to the menu.
local reset_err_file=$(mktemp)
if ! tpmr.sh reset "$tpm_owner_passphrase" >"$reset_err_file" 2>&1; then
ERROR=$(tail -n 1 "$reset_err_file" | fold -s)
rm -f "$reset_err_file"
whiptail_error --title 'ERROR' \
--msgbox "Error resetting TPM:\n\n${ERROR}" 0 80
return 1
fi
# now that the TPM is reset, remove invalid TPM counter files
mount_boot
mount -o rw,remount /boot
#TODO: this is really problematic, we should really remove the primary handle hash
STATUS "Removing rollback and primary handle hashes under /boot"
DEBUG "Removing /boot/kexec_rollback.txt and /boot/kexec_primhdl_hash.txt"
rm -f /boot/kexec_rollback.txt
rm -f /boot/kexec_primhdl_hash.txt
# create Heads TPM counter before any others
check_tpm_counter /boot/kexec_rollback.txt "" "$tpm_owner_passphrase" ||
DIE "Unable to find/create tpm counter"
TRACE_FUNC
TPM_COUNTER=$(cut -d: -f1 </tmp/counter)
DEBUG "TPM_COUNTER: $TPM_COUNTER"
#TPM_COUNTER can be empty
increment_tpm_counter "$TPM_COUNTER" "$tpm_owner_passphrase" ||
DIE "Unable to increment tpm counter"
DO_WITH_DEBUG sha256sum /tmp/counter-$TPM_COUNTER >/boot/kexec_rollback.txt ||
DIE "Unable to create rollback file"
TRACE_FUNC
# As a countermeasure for existing primary handle hash, we will now force sign /boot without it.
# NOTE: At seal time, PCR5 is IGNORED (not measured) - only used on HOTP board variants. So USB
# modules loading here don't affect DUK seal. GPG card needs USB to be enabled first.
STATUS "Preparing USB and GPG signing key access for /boot signing"
enable_usb
if wait_for_gpg_card; then
STATUS_OK "USB initialized and GPG card is accessible for /boot signing"
else
WARN "GPG card was not accessible during /boot signing preparation; retrying through key checks"
fi
while true; do
GPG_KEY_COUNT=$(gpg -K 2>/dev/null | wc -l)
if [ "$GPG_KEY_COUNT" -eq 0 ]; then
prompt_missing_gpg_key_action || return 1
wait_for_gpg_card || true
else
if ! update_checksums; then
whiptail_error --title 'ERROR' \
--msgbox "Failed to update checksums / sign default config" 0 80
return 1
fi
break
fi
done
mount -o ro,remount /boot
# Reset completed and reseal prerequisites were rebuilt.
# Clear stale preflight marker before generating fresh TOTP/HOTP.
clear_tpm_reset_required
if ! generate_totp_hotp "$tpm_owner_passphrase"; then
return 1
fi
if [ -s /boot/kexec_key_devices.txt ] || [ -s /boot/kexec_key_lvm.txt ]; then
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
fi
/bin/reboot.sh
fi
fi
}
select_os_boot_option() {
TRACE_FUNC
mount_boot