Skip to content

Commit 3f2fe25

Browse files
committed
Harden TPM reseal flows and document QEMU debugging
- make TPM reset path automatically re-sign /boot and auto-reseal DUK when key metadata exists - keep DUK reseal enforced across TPM reset and TOTP/HOTP reseal paths - improve integrity-gate diagnostics and clean up duplicate/noisy trace/debug/comment artifacts - refine integrity investigation/reporting behavior and detached-signature debug handling - document canokey state reuse between QEMU build dirs and TPM2 PCAP capture workflow - ignore main dir *.asc files Signed-off-by: Thierry Laurion <insurgo@riseup.net>
1 parent 8c49b4d commit 3f2fe25

File tree

8 files changed

+363
-57
lines changed

8 files changed

+363
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
*.asc
12
*.bad
23
*.bz2
34
*.cpio

boards/qemu-coreboot-fbwhiptail-tpm2/qemu-coreboot-fbwhiptail-tpm2.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ export CONFIG_PRIMARY_KEY_TYPE=ecc
7979
export CONFIG_DEBUG_OUTPUT=y
8080
export CONFIG_ENABLE_FUNCTION_TRACING_OUTPUT=y
8181
#Enable TPM2 pcap output under /tmp
82+
# When enabled, tpmr writes TPM2 command/response capture to /tmp/tpm0.pcap
83+
# (inside the Heads runtime). This can be inspected with Wireshark to debug
84+
# TPM interaction similarly to a TPM bus sniffer.
8285
export CONFIG_TPM2_CAPTURE_PCAP=y
8386
#Enable quiet mode: technical information logged under /tmp/debug.log
8487
export CONFIG_QUIET_MODE=n

initrd/bin/gui-init

Lines changed: 123 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export BG_COLOR_MAIN_MENU="normal"
1515
# reset when we reach the main menu so the user can retry from the main menu and
1616
# # see errors again.
1717
skip_to_menu="false"
18+
INTEGRITY_GATE_REQUIRED="n"
1819

1920
mount_boot() {
2021
TRACE_FUNC
@@ -115,23 +116,44 @@ verify_global_hashes() {
115116
less /tmp/hash_output_mismatches
116117
#move outdated hash mismatch list
117118
mv /tmp/hash_output_mismatches /tmp/hash_output_mismatch_old
118-
TEXT="Would you like to update your checksums now?"
119+
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?"
119120
else
120-
TEXT="The following files failed the verification process:\n\n${CHANGED_FILES}\n\nThis could indicate a compromise!\n\nWould you like to update your checksums now?"
121+
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?"
121122
fi
122123
fi
123124

124-
if (whiptail_error --title 'ERROR: Boot Hash Mismatch' --yesno "$TEXT" 0 80); then
125-
if update_checksums; then
126-
BG_COLOR_MAIN_MENU="normal"
127-
return 0
128-
else
129-
whiptail_error --title 'ERROR' \
130-
--msgbox "Failed to update checksums / sign default config" 0 80
131-
fi
132-
fi
133-
BG_COLOR_MAIN_MENU="error"
134-
return 1
125+
while true; do
126+
TRACE_FUNC
127+
whiptail_error --title 'ERROR: Boot Hash Mismatch' \
128+
--menu "$TEXT\n\nChoose an action:" 0 80 8 \
129+
'i' ' Investigate discrepancies -->' \
130+
'u' ' Update checksums now' \
131+
'm' ' Return to main menu' \
132+
2>/tmp/whiptail || {
133+
BG_COLOR_MAIN_MENU="error"
134+
return 1
135+
}
136+
137+
option=$(cat /tmp/whiptail)
138+
case "$option" in
139+
i)
140+
investigate_integrity_discrepancies
141+
;;
142+
u)
143+
if update_checksums; then
144+
BG_COLOR_MAIN_MENU="normal"
145+
return 0
146+
else
147+
whiptail_error --title 'ERROR' \
148+
--msgbox "Failed to update checksums / sign default config" 0 80
149+
fi
150+
;;
151+
m | *)
152+
BG_COLOR_MAIN_MENU="error"
153+
return 1
154+
;;
155+
esac
156+
done
135157
fi
136158
}
137159

@@ -146,6 +168,63 @@ prompt_update_checksums() {
146168
fi
147169
}
148170

171+
gate_reseal_with_integrity_report() {
172+
TRACE_FUNC
173+
local token_ok="y"
174+
175+
if [ "$INTEGRITY_GATE_REQUIRED" != "y" ]; then
176+
DEBUG "Skipping integrity gate: no TOTP/HOTP failure context"
177+
return 0
178+
fi
179+
180+
INTEGRITY_REPORT_HASH_STATE="UNKNOWN"
181+
report_integrity_measurements
182+
local report_rc=$?
183+
DEBUG "gate_reseal_with_integrity_report: report_integrity_measurements rc=$report_rc"
184+
DEBUG "gate_reseal_with_integrity_report: INTEGRITY_REPORT_HASH_STATE=$INTEGRITY_REPORT_HASH_STATE"
185+
if [ "$INTEGRITY_REPORT_HASH_STATE" != "OK" ]; then
186+
DEBUG "returned from integrity report, now running investigation"
187+
if ! investigate_integrity_discrepancies; then
188+
DEBUG "investigation indicated problem, aborting gate"
189+
return 1
190+
fi
191+
192+
DEBUG "gate_reseal_with_integrity_report: about to verify detached signature"
193+
DEBUG "ls -l /boot/kexec.sig: $(ls -l /boot/kexec.sig 2>/dev/null || echo missing)"
194+
if ! detached_kexec_signature_valid /boot; then
195+
DEBUG "detached_kexec_signature_valid failed"
196+
whiptail_error --title 'ERROR: Signature Verification Failed' \
197+
--msgbox "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." 0 80
198+
return 1
199+
fi
200+
else
201+
DEBUG "gate_reseal_with_integrity_report: integrity is OK, skipping investigation and detached signature verification"
202+
fi
203+
204+
if [ -x /bin/hotp_verification ]; then
205+
token_ok="n"
206+
while [ "$token_ok" != "y" ]; do
207+
enable_usb
208+
if hotp_verification info >/dev/null 2>&1; then
209+
token_ok="y"
210+
break
211+
fi
212+
if ! whiptail_warning --title "USB Security Dongle Required" \
213+
--yes-button "Retry" --no-button "Abort" \
214+
--yesno "Your USB security dongle must be present before sealing new secrets.\n\nInsert the dongle and choose Retry, or Abort." 0 80; then
215+
return 1
216+
fi
217+
done
218+
fi
219+
220+
if ! whiptail_warning --title 'Integrity Gate Passed' \
221+
--yesno "Integrity checks completed.\n\nProceed with TOTP/HOTP reseal action?" 0 80; then
222+
return 1
223+
fi
224+
INTEGRITY_GATE_REQUIRED="n"
225+
return 0
226+
}
227+
149228
generate_totp_hotp() {
150229
TRACE_FUNC
151230
tpm_owner_password="$1" # May be empty, will prompt if needed and empty
@@ -216,16 +295,14 @@ update_totp() {
216295
if [ "$CONFIG_TPM" != "y" ]; then
217296
TOTP="NO TPM"
218297
else
219-
TOTP=$(unseal-totp)
298+
TOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-totp)
220299
if [ $? -ne 0 ]; then
300+
INTEGRITY_GATE_REQUIRED="y"
221301
BG_COLOR_MAIN_MENU="error"
222302
if [ "$skip_to_menu" = "true" ]; then
223303
return 1 # Already asked to skip to menu from a prior error
224304
fi
225305

226-
DEBUG "CONFIG_TPM: $CONFIG_TPM"
227-
DEBUG "CONFIG_TPM2_TOOLS: $CONFIG_TPM2_TOOLS"
228-
DEBUG "Show PCRs"
229306
DEBUG "$(pcrs)"
230307

231308
whiptail_error --title "ERROR: TOTP Generation Failed!" \
@@ -245,7 +322,7 @@ update_totp() {
245322
option=$(cat /tmp/whiptail)
246323
case "$option" in
247324
g)
248-
if (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
325+
if gate_reseal_with_integrity_report && (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
249326
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80); then
250327
if generate_totp_hotp && update_totp && BG_COLOR_MAIN_MENU="normal"; then
251328
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
@@ -257,14 +334,16 @@ update_totp() {
257334
return 1
258335
;;
259336
p)
260-
if reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
337+
if gate_reseal_with_integrity_report && reset_tpm && update_totp && BG_COLOR_MAIN_MENU="normal"; then
261338
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
262339
fi
263340
;;
264341
x)
265342
recovery "User requested recovery shell"
266343
;;
267344
esac
345+
else
346+
INTEGRITY_GATE_REQUIRED="n"
268347
fi
269348
fi
270349
}
@@ -286,7 +365,7 @@ update_hotp() {
286365
return
287366
fi
288367
fi
289-
HOTP=$(unseal-hotp)
368+
HOTP=$(HEADS_NONFATAL_UNSEAL=y unseal-hotp)
290369
# Don't output HOTP codes to screen, so as to make replay attacks harder
291370
hotp_verification check "$HOTP"
292371
case "$?" in
@@ -308,9 +387,7 @@ update_hotp() {
308387
fi
309388

310389
if [[ "$HOTP" = "Invalid code" ]]; then
311-
#Do not propose to generate a new secret if there is no /boot/kexec_hotp_counter
312-
# tpm unseal succeeded: so the sealed secret is correct: we should propose to reset TPM if not already
313-
# Here: the OS was most probably reinstalled since TPM can still unseal the secret
390+
INTEGRITY_GATE_REQUIRED="y"
314391
whiptail_error --title "ERROR: HOTP Validation Failed!" \
315392
--menu "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?" 0 80 4 \
316393
'g' ' Generate new TOTP/HOTP secret' \
@@ -321,9 +398,11 @@ update_hotp() {
321398
option=$(cat /tmp/whiptail)
322399
case "$option" in
323400
g)
324-
if (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
401+
if gate_reseal_with_integrity_report && (whiptail_warning --title 'Generate new TOTP/HOTP secret' \
325402
--yesno "This will erase your old secret and replace it with a new one!\n\nDo you want to proceed?" 0 80); then
326-
if generate_totp_hotp && BG_COLOR_MAIN_MENU="normal"; then
403+
if generate_totp_hotp; then
404+
update_totp || true
405+
BG_COLOR_MAIN_MENU="normal"
327406
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
328407
fi
329408
fi
@@ -335,6 +414,8 @@ update_hotp() {
335414
recovery "User requested recovery shell"
336415
;;
337416
esac
417+
else
418+
INTEGRITY_GATE_REQUIRED="n"
338419
fi
339420
}
340421

@@ -449,6 +530,7 @@ show_options_menu() {
449530
--menu "" 0 80 10 \
450531
'b' ' Boot Options -->' \
451532
't' ' TPM/TOTP/HOTP Options -->' \
533+
'i' ' Investigate integrity discrepancies -->' \
452534
'h' ' Change system time' \
453535
'u' ' Update checksums and sign all files in /boot' \
454536
'c' ' Change configuration settings -->' \
@@ -470,6 +552,9 @@ show_options_menu() {
470552
t)
471553
show_tpm_totp_hotp_options_menu
472554
;;
555+
i)
556+
investigate_integrity_discrepancies
557+
;;
473558
h)
474559
change-time.sh
475560
;;
@@ -545,12 +630,12 @@ show_tpm_totp_hotp_options_menu() {
545630
option=$(cat /tmp/whiptail)
546631
case "$option" in
547632
g)
548-
if generate_totp_hotp; then
633+
if gate_reseal_with_integrity_report && generate_totp_hotp; then
549634
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
550635
fi
551636
;;
552637
r)
553-
if reset_tpm; then
638+
if gate_reseal_with_integrity_report && reset_tpm; then
554639
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
555640
fi
556641
;;
@@ -616,19 +701,24 @@ reset_tpm() {
616701
GPG_KEY_COUNT=$(gpg -k 2>/dev/null | wc -l)
617702
if [ "$GPG_KEY_COUNT" -eq 0 ]; then
618703
prompt_missing_gpg_key_action
619-
elif (whiptail --title 'TPM Reset Successfully' \
620-
--yesno "Would you like to update the checksums and sign all of the files in /boot?\n\nYou will need your GPG key to continue and this will modify your disk.\n\nOtherwise the system will reboot immediately." 0 80); then
704+
else
705+
DEBUG "TPM reset successful: updating checksums/signatures without additional confirmation"
621706
if ! update_checksums; then
622707
whiptail_error --title 'ERROR' \
623708
--msgbox "Failed to update checksums / sign default config" 0 80
709+
return 1
624710
fi
625-
else
626-
warn "TPM reset successful, but user chose not to update+sign /boot checksums. Rebooting"
627-
reboot
628711
fi
629712
mount -o ro,remount /boot
630713

631-
generate_totp_hotp "$tpm_owner_password"
714+
if ! generate_totp_hotp "$tpm_owner_password"; then
715+
return 1
716+
fi
717+
718+
if [ -s /boot/kexec_key_devices.txt ] || [ -s /boot/kexec_key_lvm.txt ]; then
719+
DEBUG "TPM reset successful: auto-resealing TPM Disk Unlock Key (DUK)"
720+
reseal_tpm_disk_decryption_key || prompt_missing_gpg_key_action
721+
fi
632722
else
633723
echo "Returning to the main menu"
634724
fi

initrd/bin/unseal-hotp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
HOTP_SECRET="/tmp/secret/hotp.key"
77
HOTP_COUNTER="/boot/kexec_hotp_counter"
88

9+
fail_unseal() {
10+
TRACE_FUNC
11+
if [ "$HEADS_NONFATAL_UNSEAL" = "y" ]; then
12+
DEBUG "nonfatal unseal-hotp failure: $*"
13+
return 1
14+
fi
15+
die "$*"
16+
}
17+
918
mount_boot_or_die() {
1019
TRACE_FUNC
1120
# Mount local disk if it is not already mounted
@@ -31,14 +40,14 @@ mount_boot_or_die
3140

3241
#if HOTP_COUNTER is not present, bail out
3342
if [ ! -f $HOTP_COUNTER ]; then
34-
die "HOTP counter file not found. If you just reinstalled an OS, you need to reseal the HOTP secret"
43+
fail_unseal "HOTP counter file not found. If you just reinstalled an OS, you need to reseal the HOTP secret" || exit 1
3544
fi
3645

3746
# Read the counter from the file
3847
counter_value=$(cat $HOTP_COUNTER 2>/dev/null)
3948

4049
if [ "$counter_value" == "" ]; then
41-
die "Unable to read HOTP counter"
50+
fail_unseal "Unable to read HOTP counter" || exit 1
4251
fi
4352

4453
#counter_value=$(printf "%d" 0x${counter_value})
@@ -47,29 +56,29 @@ if [ "$CONFIG_TPM" = "y" ]; then
4756
# ensure primary handle exists before any TPM2 operation, to keep
4857
# messaging consistent with unseal-totp
4958
if [ ! -f "/tmp/secret/primary.handle" ]; then
50-
die "Unable to unseal HOTP secret from TPM; no TPM primary handle. Reset the TPM (Options -> TPM/TOTP/HOTP Options -> Reset the TPM in the GUI)."
59+
fail_unseal "Unable to unseal HOTP secret from TPM; no TPM primary handle. Reset the TPM (Options -> TPM/TOTP/HOTP Options -> Reset the TPM in the GUI)." || exit 1
5160
fi
5261
fi
5362
DEBUG "Unsealing HOTP secret reuses TOTP sealed secret..."
5463
# debug unseal too; no password argument
5564
if ! DO_WITH_DEBUG tpmr unseal 4d47 0,1,2,3,4,7 312 "$HOTP_SECRET"; then
5665
if counter_readable; then
57-
die "Unable to unseal HOTP secret from TPM; TPM rollback counter intact. Use the GUI menu (Options -> TPM/TOTP/HOTP Options -> Generate new TOTP/HOTP secret) to reseal."
66+
fail_unseal "Unable to unseal HOTP secret from TPM; TPM rollback counter intact. Use the GUI menu (Options -> TPM/TOTP/HOTP Options -> Generate new TOTP/HOTP secret) to reseal." || exit 1
5867
else
59-
die "Unable to unseal HOTP secret from TPM; TPM rollback counter broken or missing, reset TPM (see Options -> TPM/TOTP/HOTP Options -> Reset the TPM) and then generate a new secret."
68+
fail_unseal "Unable to unseal HOTP secret from TPM; TPM rollback counter broken or missing, reset TPM (see Options -> TPM/TOTP/HOTP Options -> Reset the TPM) and then generate a new secret." || exit 1
6069
fi
6170
fi
6271
else
6372
# without a TPM, generate a secret based on the SHA-256 of the ROM
64-
secret_from_rom_hash >"$HOTP_SECRET" || die "Reading ROM failed"
73+
secret_from_rom_hash >"$HOTP_SECRET" || fail_unseal "Reading ROM failed" || exit 1
6574
fi
6675

6776
# Truncate the secret if it is longer than the maximum HOTP secret
6877
truncate_max_bytes 20 "$HOTP_SECRET"
6978

7079
if ! hotp $counter_value <"$HOTP_SECRET"; then
7180
shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null
72-
die 'Unable to compute HOTP hash?'
81+
fail_unseal 'Unable to compute HOTP hash?' || exit 1
7382
fi
7483

7584
shred -n 10 -z -u "$HOTP_SECRET" 2>/dev/null
@@ -85,7 +94,7 @@ mount -o remount,rw /boot
8594
DEBUG "Incrementing HOTP counter under $HOTP_COUNTER"
8695
counter_value=$(expr $counter_value + 1)
8796
echo $counter_value >$HOTP_COUNTER ||
88-
die "Unable to create hotp counter file"
97+
fail_unseal "Unable to create hotp counter file" || exit 1
8998
mount -o remount,ro /boot
9099

91100
exit 0

0 commit comments

Comments
 (0)