Skip to content

GPG User Authentication: In-memory gpg keygen + keytocard and GPG key material backup enabling (plus a lot of code cleanup and UX improvements) - #1515

Merged
tlaurion merged 69 commits into
linuxboot:masterfrom
tlaurion:inmemory_keygen-gpg_backup_usable_for_RSA_only-copy_to_card_working_for_RSA_only-gpg_auth_for_recovery_and_sub_boot
Nov 13, 2023
Merged

GPG User Authentication: In-memory gpg keygen + keytocard and GPG key material backup enabling (plus a lot of code cleanup and UX improvements)#1515
tlaurion merged 69 commits into
linuxboot:masterfrom
tlaurion:inmemory_keygen-gpg_backup_usable_for_RSA_only-copy_to_card_working_for_RSA_only-gpg_auth_for_recovery_and_sub_boot

Conversation

@tlaurion

@tlaurion tlaurion commented Oct 20, 2023

Copy link
Copy Markdown
Collaborator

Goal of this PR: Permit User Authentication prior of going to Recovery Shell or USB boot. But prior of enabling that functionality, we need to make sure that the user has a backup of his key material. And to have a backup of key material, we need to generate master key and subkeys in memory and backup over encrypted partition and then keytocard the subkeys to the USB Security dongle. This PR does that and clean the code that was reviewed along the way.

EDIT: this comment from @JonathonHall-Purism shows the new flow graphically: #1515 (comment)

Ok! from #1515 (comment):

  • oem-factory-reset script can be asked to be launched early at boot with the 'o' key (just as 'r' key goes to recovery shell if permitted in boot policy)
  • Purism OEM Factory Reset default workflow is now promoted by default, leaving advanced prompts to acknowledge not using defaults.
  • A questionnaire is introduced when defaults are not accepted, guiding users into properly answer prompts or entering Enter key when answer is unknown.
  • Questionnaire asks if the user wants to create an encrypted thumb drive backup of GPG key material, which when enabled currently enables GPG Authentication by default
  • Next question in questionnaire is by default Y to copy keys to USB Security dongle. p256 is enabled automatically when a NK3 is connected.
  • When enabled, GPG Authentication now authenticates prior of recovery shell access or USB boot.

Unrelated work under this PR:

  • Streamlining of tpm owner password usage across codebase, where if invalid, is now cleaned by tpmr directly.
  • When TPM Disk Unlock Key is enabled on either TPM1/TPM2, upgrading firmware past TPM TOTP/HOTP resealing reuses encrypted disks configuration and only prompts to reseal DUK with Disk Recovery Key passphrase and DUK passphrase, and then uses GPG authentication method to detach sign required changes and reboots.
  • A lot of cleanup and wording consolidation has been done.

Discussions on how GPG Authentication and OEM Factory Reset/Re-Ownership features should evolve are happening under #1520 and #1521 respectively.

Please participate, your voice is important to be considered!


Old:
Creating WiP PR to track changes.

WARNING: At current stage, prior merged PR #1476 makes it possible (warning with Intel 3rd gen I3 laptops without RDRAND: crng is not ready early on boot, will need to update board configs for notice even though those CPUs are not widely used).

Raw notes:

  • oem-factory-reset on extended provisioning mode (non-default) gives possibility of creating in-memory key generation for GPG key material (m option), creates a GPG backup thumb drive with selection of percentage of drive to be assigned to LUKS container's ext4 partition (private key container) and leftover of drive assigned to exfat partition (which contains public key)
  • oem-factory-reset also does proper keytocard of subkeys to USB Security dongle (warning: NK3 supports RSA only under pre-release version >= v1.3.1-test.20230417 where upgrading NK3 firmware is not so straightforward (impossible under debian-12, was successful with workarounds under debian-11
    • TODO: support in-memory p256 GPG algo and revert PoC changes under usb_security_token_capabilities_check (My only testing key is NK3 and I wanted to support RSA first.... so stay tuned here for latest changes and squashing prior of testing this if you have NK3 and not testing pre-releases, this is not ready to use).
  • When a GPG key material thumb drive backup is created, codebase activates Heads authentication on usb boot and recovery shell codepaths. It works in current state if you own a NitrokeyPro/Librem Key/Nitrokey Storage you are willing to wipe and dedicate for testing of this PR
  • Consequently, this PoC PR permits in-memory key gen of RSA key (2048 bits for testing here both smartcard keygen/in-memory keygen + keytocard, which sets CONFIG_HAVE_GPG_KEY_BACKUP and saves it in config.user, injects it and then checks against in in codebase to automatically activate authentication on recovery shell and usb boot codepath

Creating PR since I came across changes needed and wanted to be able to discuss them with stakeholders prior of proposing final version. Will comment on those in next comments, and will come back to OP and modify it with current state as I progress up to proposing final version. Will set PR as draft after creating PR.

@tlaurion
tlaurion marked this pull request as draft October 20, 2023 18:33
@tlaurion

Copy link
Copy Markdown
Collaborator Author

2023-10-20-140942

As you can see in this screenshot, some guidelines were added in warnings so that the user is made aware of signing errors that can happen under Heads if TPM reset is needed to re-create TPM counter which otherwise sealing operations fail. As of now, the user faces a loop when doing TPM Disk Unlock Key resealing and is now informed that he should do a TPM reset in such corner case.

@tlaurion

tlaurion commented Oct 20, 2023

Copy link
Copy Markdown
Collaborator Author

As of now, after having re-owned/oem factory reset secrets accepting to create a GPG thumb drive backup, the user can use either the backup or the USB security dongle, interchangeably:
2023-10-20-144113

On recovery/usb boot, we now have maybe too verbose output but those access are authenticated with either USB Security dongle or the Thumb drive backup:
2023-10-20-144612

@tlaurion

Copy link
Copy Markdown
Collaborator Author

Some unrelated changes in vocabulary for consistency in current state of PR when resealing TPM Disk Unlock Key
2023-10-20-145348

@tlaurion
tlaurion force-pushed the inmemory_keygen-gpg_backup_usable_for_RSA_only-copy_to_card_working_for_RSA_only-gpg_auth_for_recovery_and_sub_boot branch from a4ab0dd to 5468082 Compare October 20, 2023 20:30
@tlaurion

Copy link
Copy Markdown
Collaborator Author

Latest commit fixes output and forces dmesg output pushed in console to be of warning level and lower if no debugging mode set in board config/through configuration menu.

Accessing the recovery shell gives something like this in production environement:
2023-10-20-163011

@tlaurion

Copy link
Copy Markdown
Collaborator Author

Last commit removes unnecessary output. Just tracing here, will hide messages later on
2023-10-20-163821

@tlaurion
tlaurion force-pushed the inmemory_keygen-gpg_backup_usable_for_RSA_only-copy_to_card_working_for_RSA_only-gpg_auth_for_recovery_and_sub_boot branch from 66c03b7 to b7c4f1a Compare October 25, 2023 21:05
@tlaurion

Copy link
Copy Markdown
Collaborator Author

With last commit b7c4f1a

NK3 is supported with ECC p256 in-memory keygen and keytocard (as well as non-officially supported RSA2048 subkeys).

It looks like this when asking recovery shell access or USB boot (non-debug qemu tpm2 whiptail):
2023-10-25-170427

Normal behavior (default) is to authenticate against USB Security dongle (NK3 here):
2023-10-25-170515

Otherwise GPG Thumb drive key material backup looks like this:
2023-10-25-170325

@tlaurion

tlaurion commented Oct 25, 2023

Copy link
Copy Markdown
Collaborator Author

Next step is to reprovision USB Security dongle (keytocard) from GPG key material backup thumb drive.
All the TODO in code will be cleaned prior of asking for final review, but implementation skeleton should be in place now, so comments welcome @daringer @JonathonHall-Purism

@tlaurion

tlaurion commented Oct 30, 2023

Copy link
Copy Markdown
Collaborator Author

As current state:

  • code can generate both in memory+ keytocard or under USB Security dongle's smartcard
  • tested with tpm1/tpm2
  • with NK3 to be consistent on shipped state (p256 with current dongle detection code based on lsbuid
  • works with RSA 2048 (if detection code hacked out) and p256 inmemory and on smartcard only

A lot of code cleanup has been made along the way.

@JonathonHall-Purism Please check left TODO in code. The check for TPM2 primary handle has been removed from now since if causes a lot of issues linked to default boot and it not being signed. That would need to be fixed on master separately and would need some advice/coop.

  • oem-factory-reset has been modified to warn qemu users that internal flashing is not supported. We could extend that, since setting config options in board config is needed alongside injecting public key manually at build time with proper make options as written in md file alongside config file. Suggestions welcome
  • oem-factory-reset now includes a questionnaire warning when default oem-factory-reset is not enforced (only librems as of now, qemu enforces it).

Here is a quick preview of what to expect:
TPM2 warning to TPM reset when primary handle is not existing:
2023-10-30-133449

Questionnaire changes:
2023-10-30-134352

2023-10-30-135330
2023-10-30-135358

Comment thread initrd/bin/kexec-select-boot Outdated
Comment thread initrd/bin/tpmr Outdated
Comment thread initrd/bin/tpmr
Comment thread initrd/etc/ash_functions

@JonathonHall-Purism JonathonHall-Purism left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tlaurion. I think the ideas here and general structure are good. I haven't tested any of it yet, but I did review it thoroughly keeping in mind it's still WIP.

Specific comments above, but as some key highlights:

  • I suggest moving the "boot authentication" (gpg_auth) to a separate PR so we can more easily discuss the goals and behavior of that feature (but I think it is a great idea and on a good path).
  • I suggested wording improvements for the new prompts in OEM reset but I think the strategy makes sense
  • I'll look into the issues with the TPM2 primary handle hash as there are some clear gaps there as we've discussed before

I looked over the new GPG functionality and don't have any problems with it, with that said I'm not really a GPG expert at this point so testing will be pretty important there IMO.

Comment thread initrd/etc/functions Outdated
Comment thread initrd/etc/functions Outdated
Comment thread initrd/etc/luks-functions Outdated
Comment thread initrd/bin/oem-factory-reset Outdated
Comment thread initrd/bin/oem-factory-reset Outdated
Comment thread initrd/bin/tpmr Outdated
Comment thread initrd/etc/ash_functions
Comment thread initrd/bin/kexec-select-boot Outdated
Comment thread initrd/etc/ash_functions Outdated
Comment thread initrd/bin/reboot
Comment thread initrd/etc/ash_functions
Comment thread initrd/bin/seal-totp Outdated
Comment thread initrd/bin/kexec-seal-key Outdated
Comment thread initrd/bin/oem-factory-reset Outdated
Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…up exists. Might want to discuss that implementation. Some functions needed to be moved from functions to ash_functions so that gpg_auth can be called from recovery function. That might need to be discussed as well, recovery could be moved from ash_functions to functions instead.

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…s linked to detach signing errors

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…not so and adapt further troubleshooting notes in code when keys cannot be accessed on media for whatever cause so user can understand what is happening when accessing GPG material on backup thumb drive

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
Signed-off-by: Thierry Laurion <insurgo@riseup.net>
Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…used with GPG key material thumb drive backup

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…should be written to file and reused since not all in same functions/files for TPM2

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
…o to recovery shell instead of rebooting

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
- Add gpg_card_factory_reset() shared function for factory-reset +
  forcesig + key-attr setting (RSA and ECC/P-256), extracted from
  oem-factory-reset.sh with explicit --passphrase-file support
- Add gpg_keytocard_subkeys() shared function: enable USB, verify card,
  move subkeys to slots 1-3 (sign, encrypt, auth)
- Add gpg_set_card_identity() shared function for cardholder name and
  login fields with explicit admin PIN
- Add gpg_reset_nk3_secret_app() shared function for Nitrokey 3 Secrets
  app reset, parameterized on admin PIN
- Replace oem-factory-reset.sh function bodies with thin wrappers that
  call the shared gpg_* functions via positional args
- Add reprovision_smartcard_from_backup(): 10-phase flow that mounts
  the LUKS backup, detects key type from imported key, mounts public
  partition, factory-resets card, restores subkeys, sets identity,
  offers ROM flash
- Add k option to GPG Management Menu (gpg-gui.sh), always visible
- Add K option in prompt_missing_gpg_key_action (gui-init.sh)
- Add clean boot wizard offering OEM reset, reprovision, or ignore
- Update doc/gpg.md, doc/configuring-keys.md, doc/architecture.md
  for the new recovery path
- Document QEMU testing workflow and hardlink preservation pattern
  in doc/qemu.md (USB flash drive workflow + reprovision test)
- Add Authentication and Resetting Configuration sections to
  doc/recovery-shell.md

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup, allowing recovery
shell access control even after dongle replacement.

Tested on Nitrokey 3: OEM factory reset creates LUKS backup + provisions
dongle successfully.  Tested on Nitrokey Pro 2: reprovision from backup
restores subkeys to (new) dongle, PIN changes, public key import, ROM
flash, and gpg_auth works after reboot.  Config wipe from Options menu
correctly triggers clean boot wizard with OEM/reprovision/ignore choice.

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
- Add gpg_card_factory_reset() shared function for factory-reset +
  forcesig + key-attr setting (RSA and ECC/P-256), extracted from
  oem-factory-reset.sh with explicit --passphrase-file support
- Add gpg_keytocard_subkeys() shared function: enable USB, verify card,
  move subkeys to slots 1-3 (sign, encrypt, auth)
- Add gpg_set_card_identity() shared function for cardholder name and
  login fields with explicit admin PIN
- Add gpg_reset_nk3_secret_app() shared function for Nitrokey 3 Secrets
  app reset, parameterized on admin PIN
- Replace oem-factory-reset.sh function bodies with thin wrappers that
  call the shared gpg_* functions via positional args
- Add reprovision_smartcard_from_backup(): 10-phase flow that mounts
  the LUKS backup, detects key type from imported key, mounts public
  partition, factory-resets card, restores subkeys, sets identity,
  offers ROM flash
- Add k option to GPG Management Menu (gpg-gui.sh), always visible
- Add K option in prompt_missing_gpg_key_action (gui-init.sh)
- Add clean boot wizard offering OEM reset, reprovision, or ignore
- Update doc/gpg.md, doc/configuring-keys.md, doc/architecture.md
  for the new recovery path
- Document QEMU testing workflow and hardlink preservation pattern
  in doc/qemu.md (USB flash drive workflow + reprovision test)
- Add Authentication and Resetting Configuration sections to
  doc/recovery-shell.md

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup, allowing recovery
shell access control even after dongle replacement.

Tested on Nitrokey 3: OEM factory reset creates LUKS backup + provisions
dongle successfully.  Tested on Nitrokey Pro 2: reprovision from backup
restores subkeys to (new) dongle, PIN changes, public key import, ROM
flash, and gpg_auth works after reboot.  Config wipe from Options menu
correctly triggers clean boot wizard with OEM/reprovision/ignore choice.

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 14, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix ownertrust import regex: use [0-9A-Fa-f] to handle lowercase
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix ownertrust import regex: use [0-9A-Fa-f] to handle lowercase
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix pub_partition derivation for p-suffixed devices: detect whether
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix ownertrust import regex: use [0-9A-Fa-f] to handle lowercase
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix pub_partition derivation for p-suffixed devices: detect whether
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix ownertrust import regex: use [0-9A-Fa-f] to handle lowercase
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix pub_partition derivation for p-suffixed devices: detect whether
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  that handles both regular (sda1) and p-suffixed (nvme0n1p1) partitions
- Fix ownertrust import regex: use [0-9A-Fa-f] to handle lowercase
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 15, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
tlaurion added a commit to tlaurion/heads that referenced this pull request Jul 16, 2026
…use, add reprovision flow

Extract shared OpenPGP smartcard management functions from
initrd/bin/oem-factory-reset.sh into initrd/etc/gpg_functions.sh
so they can be reused by multiple callers.  OEM functions become
thin wrappers passing global variables as positional args; the
reprovision flow calls the same shared functions to restore GPG
subkeys from a LUKS-encrypted backup USB onto a replacement USB
Security dongle.

Shared functions (replacing OEM inline code):
- gpg_card_factory_reset(): factory-reset + forcesig + key-attr
  (RSA and ECC/P-256), parameterized on admin PIN
- gpg_keytocard_subkeys(): enable USB, verify card, move subkeys
  to slots 1-3 (sign, encrypt, auth)
- gpg_set_card_identity(): cardholder name and login fields
- gpg_reset_nk3_secret_app(): Nitrokey 3 Secrets app reset
- gpg_card_change_pin(): GPG User PIN change

New reprovision flow (reprovision_smartcard_from_backup()):
10-phase flow: mount LUKS backup -> detect key type -> factory-
reset card -> import subkeys -> set identity -> sign /boot -> flash

GUI integration:
- k option in GPG Management Menu (gpg-gui.sh), always visible
- K option in prompt_missing_gpg_key_action (gui-init.sh)
- Clean boot wizard: OEM reset, reprovision, or ignore

Code quality fixes from review:
- Fix 6 sites where $? was clobbered by TRACE_FUNC/DEBUG calls
- Replace --passphrase-file with --passphrase-fd 3 3< to avoid
  leaking PIN through /proc/<pid>/cmdline
- Fix algo_code/bit_len extraction: head -1 for multi-key backups
- Fix STATUS_OK mount emission: only on success
- Fix gpg_reset_nk3_secret_app: return actual error code from
  hotp_verification, explicit return 0 for non-NK3
- Remove incorrect || [ $? -eq 2 ] patch: gpg --import exit code 2
  is a fatal error, not unchanged
- Fix pubkey.asc import: check success properly, fall back to
  keyring export on failure
- Fix partition derivation for NVMe/MMC: stable [0-9]+$ regex
- Add _luks_cleanup helper for every error return path (replaces
  broken EXIT trap pattern)
- Re-run NK3 Secrets app reset with custom PIN on factory-reset
  retry
- Add chmod 600 on /tmp/secret/gpg_pin
- Write backup passphrase to /tmp/secret/backup_pass, use
  mount-usb.sh --pass-file to avoid argv leak
- Shred /tmp/secret/backup_pass on mount success and failure
- Shred /tmp/secret/gpg_pin after all GPG operations complete
- Limit key_id derivation to first match (head -1) to prevent
  multi-line values breaking gpg --edit-key calls
- Strip trailing <email> from cardholder name when no comment
  is present in UID line
- Reset card_admin_pin to default after factory-reset step so
  subsequent forcesig/key-attr use the correct PIN
- Ensure /tmp/secret exists before writing backup_pass (mkdir -p)
- Export public key from keyring instead of referencing mount
  file: after successful pubkey.asc import, re-export from the
  keyring to /tmp/reprovision_pubkey.asc rather than pointing
  PUBKEY at the potentially-stale /media/pubkey.asc
- Fix gpg_reset_nk3_secret_app header comment: removes
  inaccurate "to factory default" phrasing
- Remove unused uid_decoded local variable from reprovision_smartcard_from_backup
- Fix key_email extraction: add head -1 to prevent multi-line
  values when UID contains multiple <...> blocks
- Fix parent_disk derivation for NVMe/MMC: use p?[0-9]+$ regex
  the original partition used a p<num> separator and reconstruct the
  public partition as parent_disk+p2 vs parent_disk+2 accordingly
  hex fingerprints from gpg --with-colons
- Add --pass-file option to mount-usb.sh: use PASS_FILE variable
  and pass path directly to cryptsetup --key-file instead of
  reading into PASS and echoing via process substitution,
  eliminating the argv leak that --pass-file was meant to fix

Documentation:
- doc/gpg.md, doc/configuring-keys.md, doc/architecture.md:
  updated for the new recovery path
- doc/qemu.md: USB flash drive workflow, hardlink preservation,
  reprovision test
- doc/recovery-shell.md: Authentication and Resetting Config
  sections

Extends PR linuxboot#1515 (2023): authentication via gpg_auth can now be
enforced with key material restored from a backup.

Tested on Nitrokey Pro 2 (reprovision end-to-end: factory reset,
keytocard, PIN change, public key import, flash, reboot, gpg_auth
works).  Tested on Nitrokey 3 (OEM factory reset creates LUKS
backup + provisions dongle).

Signed-off-by: Thierry Laurion <insurgo@riseup.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants