Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ assignees: ''

4. What version of Heads/coreboot are you running?
- Navigate to **Options → System Information** on the running device and paste the **full version string** here (including the git commit hash).
- Alternatively, provide the GitHub commit ID if building from source.
- Alternatively, provide the **exact ROM filename** (e.g. `heads-x230-20260327-202007-my-branch-v0.2.1-42-g0b9d8e4.rom`) — it encodes the build timestamp, branch, and commit and is the fastest way to identify your build.
- Or provide the GitHub commit ID if building from source.

5. In building the rom, where did you get the blobs?
- [ ] No blobs required
Expand Down
167 changes: 167 additions & 0 deletions doc/configuring-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Configuring Keys: OEM Factory Reset / Re-Ownership

This is the primary provisioning step after installing an OS or receiving a
new Heads-equipped device. It configures all security components in one pass.

## Before You Start

**Use a safe environment.** Passphrases are echoed to the screen during setup.

**Prepare Diceware passphrases in advance.** Heads displays a QR code linking
to the EFF Diceware wordlist when you enter the questionnaire. Using physical
dice against a wordlist produces passphrases that are both strong and memorable.
See [Keys](keys.md) for recommended lengths per secret.

**You need:**
- A USB Security dongle with OpenPGP support (Nitrokey Pro 2, Nitrokey Storage 2,
Nitrokey 3, or Purism Librem Key for full HOTP support; YubiKey 5 for
OpenPGP-only).
- An OS installed on a dedicated `/boot` partition.
- Optionally: a USB thumb drive to back up GPG key material (recommended).

## Entering OEM Factory Reset / Re-Ownership

From the Heads main menu: `Options -> OEM Factory Reset / Re-Ownership`.

The wizard first shows a warning describing what will be erased. Confirm to
continue.

## Default vs. Custom Configuration

`Would you like to use default configuration options? [Y/n]`

**Answer N.** Accepting defaults leaves all security components at factory
PINs and passphrases (12345678 / 123456) which are publicly known. Custom
configuration only happens once per ownership; take the time to do it properly.

When you answer N, Heads displays a **QR code for the EFF Diceware wordlist**
— scan it with a phone to access the wordlist while entering passphrases.

## Questionnaire

### LUKS Disk Recovery Key Passphrase

`Would you like to change the current LUKS Disk Recovery Key passphrase?`

Answer **Y** if you did not install the OS yourself. The passphrase set at
OS install is unknown to you and may be known to the installer.

### LUKS Re-encryption

`Would you like to re-encrypt the LUKS container and generate a new LUKS Disk Recovery Key?`

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

### GPG Key Storage

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

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

If you answered Y:

`Would you like in-memory generated subkeys to be copied to the USB Security dongle's OpenPGP smartcard?`

Answer **Y** (recommended). Answering N leaves keys only on the backup drive;
clone it to a second drive for redundancy.

### Passphrase Strategy

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

Not recommended — using one passphrase for everything means compromising one
secret compromises all. Useful only for OEM provisioning workflows.

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

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

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

### Custom GPG Key Identity

`Would you like to set custom user information for the GnuPG key?`

Answer **Y** if you plan to use the dongle for personal signing/encryption or
want the public key to be searchable on keyservers.

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

## Key Generation

After the questionnaire, Heads:

1. Factory-resets the OpenPGP smartcard
2. Enables forced-signature PIN (good security practice)
3. Generates the GPG key (on-card or in-memory per your choice)
4. Sets the smartcard cardholder name and login fields from your identity info
5. Changes GPG Admin and User PINs to your chosen values
6. Backs up key material to the USB thumb drive (if requested)
7. Adds the new public key to the firmware and reflashes the BIOS
8. Generates `/boot` hashes and signs them
9. Resets the TPM with your chosen passphrase
10. Generates TOTP/HOTP secret and displays QR code for phone enrollment

RSA key generation on older dongles (Nitrokey Pro, Librem Key) may take
10 minutes or more — be patient.

## Provisioned Secrets Summary

At the end, Heads displays all provisioned secrets on screen and encodes them
in a QR code. **This is the last time these values are shown.** Write them
down or scan the QR code to a secure location before continuing.

## After Provisioning

### TOTP (smartphone)

Scan the QR code into Google Authenticator, FreeOTP+, or a compatible app.
On subsequent boots Heads displays the current TOTP; compare it against
your phone. Requires correct UTC time set in `Options -> Time`.

### HOTP (USB Security dongle)

Heads seals the secret to the dongle automatically. On subsequent boots the
dongle verifies the HOTP code and shows a green LED (pass) or red LED (fail).

### TPM Disk Unlock Key (optional)

Go to `Options -> Boot Options`, select a default boot option, and answer
the prompts to seal a disk unlock key in the TPM. This requires your Disk
Recovery Key passphrase and GPG User PIN. On subsequent boots the TPM
releases the key automatically when PCRs match.

## Adding an Existing GPG Key

If you already have a provisioned USB Security dongle:

1. Insert the dongle and the USB drive containing your public key.
2. Go to `Options -> GPG Management -> Add a GPG key to the running BIOS + reflash`.
3. Follow the steps. After reflashing, reboot.
4. Generate a new TOTP/HOTP secret when prompted.

## Forgotten GPG User PIN

From Recovery Shell with the dongle inserted:

```
gpg --change-pin
```

Enter the Admin PIN when prompted, then set a new User PIN.

**Warning:** 3 consecutive wrong Admin PIN attempts permanently locks the
card. There is no recovery from an exhausted Admin PIN counter short of a
full factory reset of the OpenPGP applet (which destroys all keys on the card).
106 changes: 106 additions & 0 deletions doc/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Development Workflow

## Commit Conventions

All commits to `linuxboot/heads` must be:

```bash
git commit -S -s -m "component: short description"
```

- **`-S`** — GPG-sign the commit (required; see [CONTRIBUTING.md](../CONTRIBUTING.md))
- **`-s`** — add `Signed-off-by:` trailer for [DCO](https://developercertificate.org/) compliance (required; CI enforces this)

### Message Format

```
component: short imperative description (72 chars max)

Optional body explaining the why, not the what. Wrap at 72 chars.
Reference issues or PRs with #NNN.

Signed-off-by: Your Name <email@example.com>
```

- **Subject line**: imperative mood ("fix", "add", "remove", not "fixed"/"adds")
- **Component prefix**: the file or subsystem changed (`oem-factory-reset`, `tpmr`, `gui-init`, `Makefile`, `doc`, etc.)
- **Body**: explain motivation and context; the diff shows what changed

### `Co-Authored-By`

Add a `Co-Authored-By:` trailer only on commits whose **primary content is
collaborative documentation** (`doc/*.md` writing). Never add it to code
fixes, features, or refactors.

```
Co-Authored-By: Name <email@example.com>
```

## Documentation: `doc/*.md` vs `heads-wiki`

| Location | Purpose | Signing required |
|----------|---------|-----------------|
| `doc/*.md` in this repo | Developer-facing: architecture, patterns, internals, build conventions | Yes (same as all commits) |
| `linuxboot/heads-wiki` | User-facing: installation, configuration, how-to guides published at osresearch.net | No (lower bar for contribution) |

Content should live in `doc/*.md` when it describes how the code works or how
to build/develop. Content should live in `heads-wiki` when it describes how a
user installs, configures, or operates a Heads-equipped device.

Over time, `doc/*.md` and the wiki may overlap; the canonical user-facing
source is the wiki.

## Build Artifacts

See [build-artifacts.md](build-artifacts.md) for the full ROM filename
convention. Quick reference:

```bash
# Release build (clean tag, e.g. v0.2.1):
heads-x230-v0.2.1.rom

# Development build (any other state):
heads-x230-20260327-202007-my-feature-branch-v0.2.1-42-g0b9d8e4-dirty.rom
# ^timestamp ^branch name ^git describe
```

The timestamp sorts builds chronologically. The branch name identifies which
PR or feature a binary corresponds to without consulting git.

When testing a development build, the ROM filename is your primary build
identifier — include it verbatim in bug reports and PR comments.

## Testing Checklist

When touching provisioning code (`oem-factory-reset`, `seal-hotpkey`,
`gui-init`):

- [ ] Run a full OEM Factory Reset / Re-Ownership with custom identity (name + email)
- [ ] Verify `gpg --card-status` reflects cardholder name and login data
- [ ] Verify dongle branding shows correctly for the attached device
- [ ] Verify TOTP/HOTP sealing succeeds after reset
- [ ] Check `/boot` signing succeeds with the new GPG key

When touching the Makefile or build system:

- [ ] Verify dev build filename includes timestamp + branch
- [ ] Verify a locally-tagged clean commit produces the short filename
- [ ] Verify `.zip` package extracts and `sha256sum -c` passes

## Coding Conventions

### Shell scripts

- All user-visible output through logging helpers: `STATUS`, `STATUS_OK`,
`INFO`, `NOTE`, `WARN`, `ERROR`, `DEBUG` (see [logging.md](logging.md))
- Interactive prompts via `INPUT` only — never raw `read`
- All interactive text output routed through `>"${HEADS_TTY:-/dev/stderr}"` to
avoid interleaving with `DO_WITH_DEBUG` buffered stdout
- Terminology: **passphrase** for TPM/LUKS secrets; **PIN** for GPG smartcard
(OpenPGP spec); never "password" in user-facing text
- Diceware references when prompting users to choose passphrases

### UX patterns

See [ux-patterns.md](ux-patterns.md) for `INPUT`, `STATUS`/`STATUS_OK`,
`DO_WITH_DEBUG`, `HEADS_TTY` routing, and PIN caching conventions.
96 changes: 96 additions & 0 deletions doc/gpg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# GPG Key Management

Heads uses a GPG key stored on the USB Security dongle's OpenPGP smartcard to
sign `/boot` contents and verify firmware integrity.

## Key Generation

Key generation happens automatically during `OEM Factory Reset / Re-Ownership`.
See [configuring-keys.md](configuring-keys.md) for the full provisioning flow.

Two generation paths are available:

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

**In-memory with backup:** master key and subkeys are generated in RAM,
backed up to an encrypted LUKS container on a USB thumb drive, then subkeys
are optionally copied to the dongle. Recommended for production environments.

## Changing PINs

### From Recovery Shell

```bash
gpg --change-pin
```

Menu options:
- `1` — Change User PIN (requires current User PIN)
- `2` — Unblock User PIN (requires Admin PIN)
- `3` — Change Admin PIN (requires current Admin PIN)

### PIN Retry Counters

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

- `3` — User PIN attempts remaining
- `0` — Reset Code not configured (factory state; not exhausted)
- `3` — Admin PIN attempts remaining

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

## Full Card Reset (last resort)

If the Admin PIN is blocked or the card is in an unrecoverable state:

```bash
gpg-connect-agent << 'EOF'
/hex
scd serialno
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
EOF
```

This sends deliberate wrong PINs to exhaust both the User and Admin PIN
counters, then issues the APDU sequence to fully reset the OpenPGP applet.
**All keys on the card are destroyed.** Run `OEM Factory Reset / Re-Ownership`
afterwards.

## Adding an Existing Public Key

If the dongle is already provisioned and you need to inject the matching
public key into the Heads firmware:

1. Copy the public key (`.asc`) to a USB drive.
2. Insert the dongle and the USB drive.
3. From Heads: `Options -> GPG Management -> Add a GPG key to the running BIOS + reflash`.
4. Reboot. Generate a new TOTP/HOTP secret when prompted.

## Nitrokey 3 Specifics

- Supports NIST P-256 ECC keys in addition to RSA — significantly faster key
generation and signing.
- Secrets app (HOTP) PIN is separate from the OpenPGP card Admin PIN.
- Physical touch confirmation is required for some operations (initialize,
key generation).
- Secrets app PIN reset is available via `Options -> OEM Factory Reset /
Re-Ownership` without a full card reset.

## TODO

- Populate the GPG key's preferred keyserver field and the card `url` field
after uploading the public key to `keys.openpgp.org`. Requires network
access in the initrd. See `set_card_identity()` in `initrd/bin/oem-factory-reset`.
Loading