Skip to content

Commit 3396cfd

Browse files
committed
initrd/gpg: refactor OEM card management into shared functions for reuse, 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 #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>
1 parent 8d0064f commit 3396cfd

10 files changed

Lines changed: 1141 additions & 364 deletions

File tree

doc/architecture.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ Linux kernel, and a security-focused initramfs. It establishes a hardware root o
55
measured boot via TPM, and verifies the OS boot environment before handing off control.
66

77
See also: [security-model.md](security-model.md), [boot-process.md](boot-process.md),
8-
[tpm.md](tpm.md) detailed subsystem documentation.
8+
[tpm.md](tpm.md) -- detailed subsystem documentation.
99

10-
External reference: [deepwiki.com/linuxboot/heads](https://deepwiki.com/linuxboot/heads)
10+
External reference: [deepwiki.com/linuxboot/heads](https://deepwiki.com/linuxboot/heads) --
1111
validated against code in this repository.
1212

1313
---
@@ -43,7 +43,7 @@ The framebuffer initialized by coreboot (libgfxinit on pre-Alder Lake,
4343
FSP GOP on Alder Lake and newer) must survive across kexec for display
4444
to work in the booted OS. Boards without a coreboot framebuffer
4545
(talos-2: kernel AST DRM, librem_l1um: BMC serial, KGPE-D16: native
46-
VGA init server variants output to BMC serial + AST VGA; workstation
46+
VGA init -- server variants output to BMC serial + AST VGA; workstation
4747
variants use external discrete NVIDIA/AMD GPUs with fbwhiptail) have
4848
different display paths that do not involve sysfb handoff.
4949
See [boot-process.md](boot-process.md#target-kernel-display-handoff)
@@ -53,7 +53,7 @@ workaround.
5353

5454
### Linux kernel (payload)
5555

56-
A minimal, stripped kernel compiled specifically for Heads. No initramfs it boots directly
56+
A minimal, stripped kernel compiled specifically for Heads. No initramfs -- it boots directly
5757
into the Heads initramfs. Provides device drivers (TPM, USB, storage, network), filesystem
5858
support, and the platform for the boot scripts.
5959

@@ -86,9 +86,9 @@ execution. Source lives in `initrd/`.
8686

8787
Three-layer hierarchy:
8888

89-
1. **`/etc/config`** Board defaults compiled into the ROM at build time
90-
2. **`/etc/config.user`** User overrides extracted from CBFS at runtime
91-
3. **`/tmp/config`** Combined result, sourced during boot
89+
1. **`/etc/config`** -- Board defaults compiled into the ROM at build time
90+
2. **`/etc/config.user`** -- User overrides extracted from CBFS at runtime
91+
3. **`/tmp/config`** -- Combined result, sourced during boot
9292

9393
`combine_configs()` in `initrd/etc/functions.sh` merges these by concatenating
9494
`/etc/config*` into `/tmp/config`. User settings in CBFS take precedence
@@ -105,12 +105,12 @@ The top-level `Makefile` orchestrates:
105105
- Cross-compiler (`musl-cross-make`, target: `x86_64-linux-musl` or `powerpc64le-linux-musl`)
106106
- Modules (coreboot, Linux, busybox, GPG, cryptsetup, kexec, LVM2, …)
107107
- Six CPIO archives assembled into the initramfs:
108-
1. `dev.cpio` device nodes
109-
2. `modules.cpio` kernel modules
110-
3. `tools.cpio` userspace tools + configuration
111-
4. `board.cpio` board-specific scripts
112-
5. `heads.cpio` security scripts (`CONFIG_HEADS=y`)
113-
6. `data.cpio` data files
108+
1. `dev.cpio` -- device nodes
109+
2. `modules.cpio` -- kernel modules
110+
3. `tools.cpio` -- userspace tools + configuration
111+
4. `board.cpio` -- board-specific scripts
112+
5. `heads.cpio` -- security scripts (`CONFIG_HEADS=y`)
113+
6. `data.cpio` -- data files
114114
- Final ROM image: coreboot ROM with Linux + initramfs payload embedded
115115

116116
Reproducible builds are achieved via Nix-pinned Docker images. See [docker.md](docker.md).
@@ -130,11 +130,11 @@ The CI pipeline's workspace and cache behavior is documented in
130130

131131
## Key design principles
132132

133-
- **No network at boot** all verification is local; no certificate authorities
134-
- **Hardware root of trust** — the coreboot bootblock (IBB) is the Static Core Root of Trust for Measurement (S-CRTM): the first code executed by the CPU, directly from SPI flash. Coreboot implements a transitive measurement chain: the CRTM measures FMAP and the bootblock image into the preram log, then each subsequent stage measures the next before executing it — bootblock measures romstage, romstage measures ramstage, ramstage measures the Heads payload. Measurements are taken during CBFS file loading, before decompression, and are recorded in TPM PCR 2 (SRTM) once the TPM hardware is initialized (`tpm_setup()`). Measurements taken before TPM init are cached in the preram log and flushed to PCR 2 by `tspi_measure_cache_to_pcr()` during `tpm_setup()`. The full chain — bootblock → romstage → ramstage → Heads Linux kernel + initrd — is recorded into PCR 2. PCRs 0, 1, and 3 remain zero as policy anchors. See [tpm.md](tpm.md#srtm-in-coreboot) for TPM init timing per board. See [wp-notes.md](wp-notes.md#pr0-chipset-locking) for SPI write-protection and PR0 chipset locking details.
135-
- **Fail-closed** failed integrity verification drops to a recovery shell. Recovery shell authentication via GPG smartcard is enforced when GPG key backup has been configured (`CONFIG_HAVE_GPG_KEY_BACKUP=y`), which is set by answering "y" to `"Would you like to format an encrypted USB Thumb drive to store GPG key material? (Required to enable GPG authentication)"` during OEM Factory Reset / Re-Ownership. Otherwise the recovery shell is unauthenticated. An "Ignore tampering and force a boot (Unsafe!)" option is available to override this.
136-
- **Separation of duties** the public key that verifies `/boot` signatures is stored in CBFS (ROM). The private key that signs `/boot` stays on a USB security dongle and never leaves it.
137-
- **Auditability** all source is open, builds are reproducible, ROM images are verifiable
133+
- **No network at boot** -- all verification is local; no certificate authorities
134+
- **Hardware root of trust** -- the coreboot bootblock (IBB) is the Static Core Root of Trust for Measurement (S-CRTM): the first code executed by the CPU, directly from SPI flash. Coreboot implements a transitive measurement chain: the CRTM measures FMAP and the bootblock image into the preram log, then each subsequent stage measures the next before executing it -- bootblock measures romstage, romstage measures ramstage, ramstage measures the Heads payload. Measurements are taken during CBFS file loading, before decompression, and are recorded in TPM PCR 2 (SRTM) once the TPM hardware is initialized (`tpm_setup()`). Measurements taken before TPM init are cached in the preram log and flushed to PCR 2 by `tspi_measure_cache_to_pcr()` during `tpm_setup()`. The full chain -- bootblock → romstage → ramstage → Heads Linux kernel + initrd -- is recorded into PCR 2. PCRs 0, 1, and 3 remain zero as policy anchors. See [tpm.md](tpm.md#srtm-in-coreboot) for TPM init timing per board. See [wp-notes.md](wp-notes.md#pr0-chipset-locking) for SPI write-protection and PR0 chipset locking details.
135+
- **Fail-closed** -- failed integrity verification drops to a recovery shell. Recovery shell authentication via GPG smartcard is enforced when GPG key backup has been configured (`CONFIG_HAVE_GPG_KEY_BACKUP=y`), which is set by answering "y" to `"Would you like to format an encrypted USB Thumb drive to store GPG key material? (Required to enable GPG authentication)"` during OEM Factory Reset / Re-Ownership, or by running "Reprovision smartcard from GPG key backup" from the GPG Management Menu. Otherwise the recovery shell is unauthenticated. An "Ignore tampering and force a boot (Unsafe!)" option is available to override this. See [recovery-shell.md](recovery-shell.md#authentication) for details.
136+
- **Separation of duties** -- the public key that verifies `/boot` signatures is stored in CBFS (ROM). The private key that signs `/boot` stays on a USB security dongle and never leaves it.
137+
- **Auditability** -- all source is open, builds are reproducible, ROM images are verifiable
138138

139139
### Purism boot modes
140140

@@ -162,13 +162,13 @@ kexec-boot.sh → lock_chip.sh → io386 0xb2 0xcb → SMI → FLOCKDN
162162
until reset
163163
```
164164

165-
This is x86/Intel only QEMU (emulated), Talos II (POWER9), Librem L1UM,
165+
This is x86/Intel only -- QEMU (emulated), Talos II (POWER9), Librem L1UM,
166166
and AMD-based boards (KGPE-D16) lack Intel chipset locking. 29 Intel
167167
boards across all generations from Sandy Bridge to Alder Lake use this
168168
mechanism.
169169

170170
See [wp-notes.md](wp-notes.md#pr0-chipset-locking) for the full config
171171
requirements, runtime chain details, and board-level status.
172172

173-
See also: [wp-notes.md](wp-notes.md) per-board write-protection
173+
See also: [wp-notes.md](wp-notes.md) -- per-board write-protection
174174
status and tracking.

doc/configuring-keys.md

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ new Heads-equipped device. It configures all security components in one pass.
88
**Use a safe environment.** Passphrases are echoed to the screen during setup.
99

1010
**Prepare Diceware passphrases in advance.** Heads displays a QR code linking
11-
to `osresearch.net/Configuring-Keys` when you enter the questionnaire that
11+
to `osresearch.net/Configuring-Keys` when you enter the questionnaire -- that
1212
page lists recommended word counts per secret and links to EFF Diceware.
1313
Using physical dice against a wordlist produces passphrases that are both
1414
strong and memorable.
@@ -37,7 +37,7 @@ PINs and passphrases (12345678 / 123456) which are publicly known. Custom
3737
configuration only happens once per ownership; take the time to do it properly.
3838

3939
When you answer N, Heads displays a **QR code for osresearch.net/Configuring-Keys**
40-
scan it with a phone for per-secret word-count guidance and EFF Diceware links.
40+
-- scan it with a phone for per-secret word-count guidance and EFF Diceware links.
4141

4242
## Questionnaire
4343

@@ -53,18 +53,18 @@ OS install is unknown to you and may be known to the installer.
5353
`Would you like to re-encrypt the LUKS container and generate a new LUKS Disk Recovery Key?`
5454

5555
Answer **Y** if you did not install the OS yourself. Changing the passphrase
56-
alone does not change the underlying encryption key anyone with a LUKS header
56+
alone does not change the underlying encryption key -- anyone with a LUKS header
5757
backup from before could still decrypt with the old passphrase. Re-encryption
5858
generates a new key and renders old header backups useless.
5959

6060
### GPG Key Storage
6161

6262
`Would you like to format an encrypted USB Thumb drive to store GPG key material?`
6363

64-
- **Y** Generates the GPG master key and subkeys in memory, backs them up
64+
- **Y** -- Generates the GPG master key and subkeys in memory, backs them up
6565
to an encrypted LUKS container on a USB thumb drive, then optionally copies
6666
subkeys to the dongle. Recommended for production environments.
67-
- **N** Generates keys directly on the dongle's OpenPGP smartcard with no
67+
- **N** -- Generates keys directly on the dongle's OpenPGP smartcard with no
6868
off-card backup. Simpler but irreversible if the dongle is lost.
6969

7070
If you answered Y:
@@ -78,16 +78,16 @@ clone it to a second drive for redundancy.
7878

7979
`Would you like to set a single custom passphrase to all security components?`
8080

81-
Not recommended using one passphrase for everything means compromising one
81+
Not recommended -- using one passphrase for everything means compromising one
8282
secret compromises all. Useful only for OEM provisioning workflows.
8383

8484
`Would you like to set distinct PINs/passphrases for each security component?`
8585

8686
Answer **Y**. You will be prompted for:
8787

88-
- **TPM Owner Passphrase** (min 8 chars) protects TPM NVRAM ownership
89-
- **GPG Admin PIN** (6-25 chars) protects smartcard management operations
90-
- **GPG User PIN** (6-25 chars) protects signing and encryption operations
88+
- **TPM Owner Passphrase** (min 8 chars) -- protects TPM NVRAM ownership
89+
- **GPG Admin PIN** (6-25 chars) -- protects smartcard management operations
90+
- **GPG User PIN** (6-25 chars) -- protects signing and encryption operations
9191

9292
### Custom GPG Key Identity
9393

@@ -96,10 +96,10 @@ Answer **Y**. You will be prompted for:
9696
Answer **Y** if you plan to use the dongle for personal signing/encryption or
9797
want the public key to be searchable on keyservers.
9898

99-
- **Real Name** your name; becomes the cardholder name on the smartcard
100-
- **Email** your email; becomes the login field on the smartcard and the
99+
- **Real Name** -- your name; becomes the cardholder name on the smartcard
100+
- **Email** -- your email; becomes the login field on the smartcard and the
101101
key UID email
102-
- **Comment** distinguishes this key (e.g. "USB Security dongle"); 1-60 chars
102+
- **Comment** -- distinguishes this key (e.g. "USB Security dongle"); 1-60 chars
103103

104104
## Key Generation
105105

@@ -119,11 +119,11 @@ After the questionnaire, Heads performs the following steps in order:
119119
12. Displays all provisioned secrets for confirmation
120120

121121
After completing, Heads shows a **reboot prompt**. TOTP/HOTP secret
122-
generation happens on the **first normal boot** after OEM reset Heads detects
122+
generation happens on the **first normal boot** after OEM reset -- Heads detects
123123
the TPM was cleared and guides you through the reseal process.
124124

125125
RSA key generation on older dongles (Nitrokey Pro, Librem Key) may take
126-
10 minutes or more be patient.
126+
10 minutes or more -- be patient.
127127

128128
## Provisioned Secrets Summary
129129

@@ -160,6 +160,23 @@ If you already have a provisioned USB Security dongle:
160160
3. Follow the steps. After reflashing, reboot.
161161
4. Generate a new TOTP/HOTP secret when prompted.
162162

163+
### Restoring Keys from Backup
164+
165+
If you chose the in-memory backup path during OEM factory reset and your
166+
dongle is lost, broken, or wiped:
167+
168+
1. Insert the backup USB thumb drive and your (new) dongle.
169+
2. Go to `Options -> GPG Management -> Reprovision smartcard from GPG key backup`.
170+
3. Enter the backup passphrase when prompted.
171+
4. Heads detects the key type from the backup, factory-resets the dongle,
172+
restores the subkeys, and sets the card identity.
173+
5. After success, flash the public key to the running BIOS for persistence.
174+
6. Re-sign /boot via `Options -> Update checksums and sign all files in /boot`.
175+
176+
This requires the LUKS-encrypted backup USB drive created during OEM factory
177+
reset (answer Y to "format an encrypted USB Thumb drive"). Without it, run
178+
a new OEM Factory Reset / Re-Ownership to rekey the device.
179+
163180
## Forgotten GPG User PIN
164181

165182
From Recovery Shell with the dongle inserted:

doc/gpg.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ See [configuring-keys.md](configuring-keys.md) for the full provisioning flow.
1111
Two generation paths are available:
1212

1313
**On-card (default):** keys are generated directly on the smartcard.
14-
No off-card backup exists losing the dongle means losing the key.
14+
No off-card backup exists -- losing the dongle means losing the key.
1515

1616
**In-memory with backup:** master key and subkeys are generated in RAM,
1717
backed up to an encrypted LUKS container on a USB thumb drive, then subkeys
@@ -77,22 +77,22 @@ gpg --change-pin
7777
```
7878

7979
Menu options:
80-
- `1` Change User PIN (requires current User PIN)
81-
- `2` Unblock User PIN (requires Admin PIN)
82-
- `3` Change Admin PIN (requires current Admin PIN)
80+
- `1` -- Change User PIN (requires current User PIN)
81+
- `2` -- Unblock User PIN (requires Admin PIN)
82+
- `3` -- Change Admin PIN (requires current Admin PIN)
8383

8484
### PIN Retry Counters
8585

8686
OpenPGP cards have separate retry counters for User PIN, Reset Code, and
8787
Admin PIN. The factory state counter reads `3 0 3`:
8888

89-
- `3` User PIN attempts remaining
90-
- `0` Reset Code not configured (factory state; not exhausted)
91-
- `3` Admin PIN attempts remaining
89+
- `3` -- User PIN attempts remaining
90+
- `0` -- Reset Code not configured (factory state; not exhausted)
91+
- `3` -- Admin PIN attempts remaining
9292

9393
When a counter reaches 0 the corresponding PIN is blocked. A blocked User
9494
PIN can be unblocked with the Admin PIN. A blocked Admin PIN **cannot be
95-
recovered** the card must be fully reset (destroying all keys).
95+
recovered** -- the card must be fully reset (destroying all keys).
9696

9797
## Full Card Reset (last resort)
9898

@@ -130,9 +130,21 @@ public key into the Heads firmware:
130130
3. From Heads: `Options -> GPG Management -> Add a GPG key to the running BIOS + reflash`.
131131
4. Reboot. Generate a new TOTP/HOTP secret when prompted.
132132

133+
### Restoring Keys from Backup
134+
135+
See [configuring-keys.md](configuring-keys.md#restoring-keys-from-backup) for
136+
the full recovery flow. In short:
137+
138+
1. Insert the backup USB thumb drive and the replacement dongle.
139+
2. From Heads: `Options -> GPG Management -> Reprovision smartcard from GPG key backup`.
140+
3. Enter the backup passphrase.
141+
142+
The backup drive must have been created by the OEM factory reset flow with
143+
the "format an encrypted USB Thumb drive" option set to Y.
144+
133145
## Nitrokey 3 Specifics
134146

135-
- Supports NIST P-256 ECC keys in addition to RSA significantly faster key
147+
- Supports NIST P-256 ECC keys in addition to RSA -- significantly faster key
136148
generation and signing.
137149
- Secrets app (HOTP) PIN is separate from the OpenPGP card Admin PIN.
138150
- Physical touch confirmation is required for some operations (initialize,

0 commit comments

Comments
 (0)