Skip to content

Latest commit

 

History

History
442 lines (358 loc) · 26.5 KB

File metadata and controls

442 lines (358 loc) · 26.5 KB

Heads Security Model

This document describes the security architecture of Heads: how trust is established, how integrity is verified at each boot, and how secrets are protected.

See also: architecture.md, tpm.md, boot-process.md, ux-patterns.md, Heads threat model for the user-facing per-board protection status.


Security Architecture Overview

Heads implements a cross-validated boot chain where multiple security mechanisms verify each other, preventing single points of failure.

┌─────────────────────────────────────────────────────────────────────────────┐
│                        HEADS CROSS-VALIDATED BOOT CHAIN                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐ │
│  │ SPI Flash   │───▶│   TPM      │───▶│  /boot      │───▶│   OS        │ │
│  │ ROM         │    │ PCRs       │    │  GPG Sig   │    │   Disk      │ │
│  │ (Hardware   │    │ (Measured) │    │  (Signed)  │    │   (LUKS)    │ │
│  │   RoT)      │    │            │    │            │    │             │ │
│  └─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘ │
│         │                 │                 │                 │            │
│         │                 │                 │                 │            │
│         ▼                 ▼                 ▼                 ▼            │
│  ┌─────────────────────────────────────────────────────────────────────┐  │
│  │                    ROLLBACK COUNTER (TPM NVRAM)                      │  │
│  │   ┌───────────────┐              ┌───────────────┐                  │  │
│  │   │ TPM NVRAM     │◀────────────▶│ /boot disk   │                  │  │
│  │   │ (counter)     │   2-way      │ kexec_       │                  │  │
│  │   │               │   binding    │ rollback.txt │                  │  │
│  │   └───────────────┘              └───────────────┘                  │  │
│  └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐  │
│  │                    TPM-SEALED SECRETS (NVRAM)                       │  │
│  │                                                                      │  │
│  │  ┌────────────────────────────────────────────────────────────────┐ │  │
│  │  │  TOTP/HOTP SHARED SECRET (NVRAM index 4d47)                 │ │  │
│  │  │  20 bytes random, sealed to PCRs 0,1,2,3,4,7               │ │  │
│  │  │                                                                │ │  │
│  │  │   Same secret ──▶ TOTP ──▶ Phone authenticator app          │ │  │
│  │  │            └──▶ Reverse HOTP ──▶ USB dongle (verifies code) │ │  │
│  │  └────────────────────────────────────────────────────────────────┘ │  │
│  │                                                                      │  │
│  │  ┌────────────────────────────────────────────────────────────────┐ │  │
│  │  │  LUKS DUK (Disk Unlock Key)                                   │ │  │
│  │  │  128 bytes random, sealed to PCRs 0,1,2,3,4,5,6,7           │ │  │
│  │  │  - PCR 5: kernel modules, PCR 6: LUKS header               │ │  │
│  │  └────────────────────────────────────────────────────────────────┘ │  │
│  └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Seal policies are listed in tpm.md.


Cross-Validation Matrix

This table shows how each component verifies the others:

Component Verifies Verified By Prevents
SPI Flash ROM TPM PCRs TPM measured boot Firmware tampering
TPM PCRs /boot files TPM unseal policy Firmware change
/boot files GPG signature ROM public key Boot config tampering
GPG signature Rollback counter hash TPM (via PCRs) Old /boot rollback
Rollback counter TPM + /boot binding Both must match TPM/boot swap attack
TOTP/HOTP PCR match (same secret) TPM unseal Firmware tampering
LUKS DUK PCR + /boot TPM unseal + sig Disk theft

Trust hierarchy

The diagram below shows the standard TPM-based boot path. For boards without TPM hardware, see HOTP on boards without a TPM.

SPI flash ROM  (hardware root of trust)
  │
  │  coreboot SRTM measures boot block + payload into PCR 2; PCRs 0,1,3 unused
  ▼
TPM PCR values  (hardware-attested firmware state)
  │
  │  Heads unseals TOTP/HOTP secret only when PCRs match expected values
  ▼
TOTP/HOTP code  (proves firmware was not tampered since last seal)
  │
  │  User verifies TOTP/HOTP matches the value on their phone/token
  ▼
User-approved boot  (human-in-the-loop verification)
  │
  │  GPG signature on /boot/kexec.sig verified against ROM-fused public key
  ▼
/boot integrity  (OS bootloader, kernel, and initrd authenticated)
  │
  │  LUKS DUK unsealed from TPM (only when PCRs match + /boot is signed)
  ▼
Decrypted OS disk  (disk encryption key delivered without passphrase prompt)

Hardware root of trust

The trust anchor is the SPI flash ROM containing coreboot. Heads treats this as the immutable starting point:

  • Coreboot measures firmware stages and the Linux payload into TPM PCR 2 (SRTM) before executing it.
  • The Linux payload is embedded in the ROM (no network, no external media required).
  • Flash write protection: On supported Intel boards, the SPI ROM is locked against writes via chipset-level PR0 lockdown just before kexec. See wp-notes.md for the full mechanism, coreboot/Heads config requirements, and board coverage.

There is no certificate authority, no boot server, and no runtime network access during the verified boot path.


Measured boot

The bootblock (IBB — Initial Boot Block) is the Static Core Root of Trust for Measurement (S-CRTM): the first code executed by the CPU, directly from SPI flash, before anything else has run. All subsequent stages are measured from it.

Coreboot's measured boot (CONFIG_TPM_MEASURED_BOOT=y) measures the full firmware chain into PCR 2 (CONFIG_PCR_SRTM=2):

bootblock → romstage → ramstage → Heads Linux kernel + initrd (payload)

On boards with CONFIG_TPM_MEASURED_BOOT=y + CONFIG_TPM_INIT_RAMSTAGE=y (the majority of maintained boards), ramstage initializes the TPM, reads each prior stage from CBFS, and extends PCR 2. Older coreboot versions (4.11) used CONFIG_TPM_INIT=y before this config key existed; some boards have no TPM hardware. See tpm.md for the PCR map and the PCR 4 boot path values.

For board-specific RoT configuration and the files that control each PCR, see tpm.md — Configuration reference for developers.


Boot attestation: TOTP and HOTP

TOTP and HOTP share the same 20-byte secret sealed to TPM NVRAM (index 4d47). The secret can only be unsealed when the firmware PCR state matches what was recorded at seal time. A firmware change causes a PCR mismatch and unseal failure, which the user observes as a TOTP/HOTP mismatch.

TOTP

A 20-byte random secret is generated when a new TOTP/HOTP secret is created, normally on the first boot after OEM Factory Reset / Reownership, and sealed to TPM NVRAM. At each boot, unseal-totp retrieves it and generates the current 30-second code. The user compares this against their authenticator app.

HOTP (Reverse HOTP)

The same secret is used for HOTP. On supported dongles (Nitrokey Pro/Storage/3, Librem Key), Heads uses reverse HOTP verification:

  1. Heads unseals the shared secret from TPM
  2. Heads computes the HOTP code using secret + counter
  3. Heads sends the computed code to the dongle
  4. Dongle verifies the code matches its own computation and signals via LED:
    • Green blinking: code verified
    • Red blinking: code mismatch

This is "reverse" because normally the dongle generates the code - here the computer generates it and the dongle verifies. The dongle provides a tamper signal independent of the display (visible before screen is initialized).

The HOTP counter is stored in /boot/kexec_hotp_counter (a plain file, not in TPM NVRAM).

HOTP on boards without a TPM (ROM-hash mode)

On boards where CONFIG_NO_TPM=y (currently the Librem Mini, Librem Mini v2, and Librem 11), there is no TPM to seal secrets against PCR values. Heads falls back to a different HOTP secret derivation implemented in secret_from_rom_hash in initrd/etc/functions.sh:

  1. At seal time, flash.sh reads the full SPI ROM via flashrom/flashprog.
  2. The first 20 bytes of the SHA-256 of the ROM image are used as the HOTP secret.
  3. The secret is programmed onto the USB security dongle.
  4. At each boot, the ROM is read again, SHA-256 recomputed, and the HOTP code sent to the dongle for comparison. A changed ROM produces a different hash, a different code, and a dongle failure signal.

The HOTP counter is stored in /boot/kexec_hotp_counter as a plain file (not in TPM NVRAM, which does not exist on these platforms).

Known limitations of ROM-hash HOTP (publicly noted):

  • The secret is deterministic and derived from public data — anyone with physical access to read the ROM can derive the HOTP secret independently, without owning the dongle.
  • No hardware platform binding: the secret is not tied to the specific hardware instance, only to ROM contents.
  • ROM reading via flashrom/flashprog at every boot expands attack surface and is slower than a TPM unseal.
  • The counter file on /boot is not TPM-protected and could in principle be manipulated to extend the HOTP window (the token accepts codes within a ±5-count lookahead window).
  • There is no equivalent of TOTP on these boards; time-based attestation without a TPM is not implemented.
  • LUKS disk encryption key sealing to TPM (DUK) is not available; disk unlock requires the user's passphrase at every boot.

The ROM-hash HOTP mode provides a weaker attestation model than the TPM-based path. Its value is in detecting ROM modifications via the dongle's LED, but it does not provide the same tamper-evident guarantees as TPM PCR sealing.

Attestation failure handling

If a TOTP unseal fails, INTEGRITY_GATE_REQUIRED is set and sealing new TPM secrets is blocked until the integrity gate passes. A failed HOTP unseal or missing token only shows a warning and returns to the menu; a failed HOTP code check sets the gate. See ux-patterns.md.


/boot integrity: GPG signatures

All files in /boot are protected by a SHA-256 hash manifest and a GPG detached signature (kexec.sig).

Signing (kexec-sign-config)

When the user installs or updates the OS, kexec-sign-config:

  1. Hashes all non-kexec* files in /boot into kexec_hashes.txt and generates a directory tree listing in kexec_tree.txt.
  2. Signs the hash manifest with a GPG key, producing kexec.sig.
  3. When signing runs with the rollback counter enabled (-r on TPM boards without CONFIG_IGNORE_ROLLBACK), increments the TPM rollback counter and stores the new counter hash in kexec_rollback.txt.

The signing key lives on a hardware security dongle (OpenPGP smartcard), never in the ROM. Signing requires physical possession of the card and knowledge of the card PIN. To reduce repeated PIN prompts within the same session, Heads caches the validated User PIN in /tmp/secret/gpg_pin (mode 600, tmpfs; cleared at power-off). See ux-patterns.md — GPG User PIN caching for the caching mechanism and its security properties.

Verification (kexec-select-boot)

At each boot, verify_global_hashes in kexec-select-boot calls verify_checksums and check_config to confirm that every /boot file matches its stored hash and that kexec.sig is valid. On mismatch, an interactive whiptail menu offers three options: investigate discrepancies, update checksums, or return to the main menu. Choosing "return" causes DIE — there is no "boot anyway" path.

The ROM contains only the public key. Verification uses gpgv with the ROM keyring; no private key material is needed at boot.


Rollback counter: TPM/boot binding

The rollback counter creates a two-way binding between the TPM hardware and the /boot disk, preventing swap attacks.

┌─────────────────────────────────────────────────────────────────────────────┐
│                     ROLLBACK COUNTER ATTACK PREVENTION                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────┐                     ┌─────────────┐                       │
│   │   OLD TPM   │                     │  OLD /boot  │                       │
│   │             │                     │             │                       │
│   │ counter=5   │                     │ hash=5      │                       │
│   │ (NVRAM)     │                     │ (disk)      │                       │
│   └─────────────┘                     └─────────────┘                       │
│         │                                   │                               │
│         │                                   │                               │
│         ▼                                   ▼                               │
│   ┌─────────────────────────────────────────────────────────┐               │
│   │          ATTACK SCENARIO: Old TPM + Old /boot           │               │
│   │                                                         │               │
│   │   Attacker uses old TPM (counter=5) with old /boot      │               │
│   │   (hash=5). This would bypass security updates!         │               │
│   │                                                         │               │
│   │   → BLOCKED: TPM unseal requires current PCR values     │               │
│   │   → BLOCKED: GPG signature must match current /boot     │               │
│   └─────────────────────────────────────────────────────────┘               │
│                                                                             │
│   ┌─────────────┐                     ┌─────────────┐                       │
│   │  NEW TPM    │                     │  OLD /boot  │                       │
│   │             │                     │             │                       │
│   │ counter=X   │                     │ hash=5      │                       │
│   │ (no secrets │                     │ (disk)      │                       │
│   │  sealed!)   │                     │             │                       │
│   └─────────────┘                     └─────────────┘                       │
│         │                                   │                               │
│         │                                   │                               │
│         ▼                                   ▼                               │
│   ┌─────────────────────────────────────────────────────────┐               │
│   │          ATTACK SCENARIO: New TPM + Old /boot           │               │
│   │                                                         │               │
│   │   Attacker swaps TPM. New TPM has no sealed secrets.    │               │
│   │   Old /boot has old counter hash.                       │               │
│   │                                                         │               │
│   │   → BLOCKED: TOTP/HOTP/DUK unseal fails (no secrets)    │               │
│   │   → BLOCKED: Rollback counter mismatch detected         │               │
│   └─────────────────────────────────────────────────────────┘               │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

The counter mechanics are in tpm.md.

A missing rollback record blocks boot only when CONFIG_BOOT_REQ_ROLLBACK=y; no board config enables that requirement. An existing record mismatch aborts boot unless CONFIG_IGNORE_ROLLBACK=y.

┌─────────────────────────────────────────────────────────────────────────────┐
│                    ROLLBACK COUNTER LIFECYCLE                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  OEM FACTORY RESET                     NORMAL BOOT                         │
│  ─────────────────                     ────────────                         │
│                                                                             │
│  1. Create counter in TPM NVRAM               1. Read counter from TPM      │
│     └─▶ create counter, then increment to 1      │                          │
│  2. Hash counter → /boot                         ▼                          │
│     └─▶ kexec_rollback.txt                    2. Hash counter               │
│                                                  │                          │
│                                                  ▼                          │
│                                               3. Compare with /boot hash    │
│                                                  │                          │
│                                                  ▼                          │
│  OS UPDATE                                    4. Match? → Continue          │
│  ──────────                                   5. Mismatch? → Die            │
│                                                                             │
│  1. kexec-sign-config runs                    TPM SEALED SECRETS            │
│     │                                         ──────────────────            │
│     ▼                                         TOTP/HOTP/DUK can only unseal │
│  2. Increment TPM counter                     if:                           │
│     └─▶ counter_value = 1                       - PCRs match seal policy    │
│  3. Hash new value → /boot                      - TPM is the SAME TPM       │
│     └─▶ kexec_rollback.txt                      - /boot is the SAME /boot   │
│                                                                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

PCR binding in TPM sealing

The seal policy for each secret is in tpm.md.


Disk encryption: LUKS DUK

The LUKS Disk Unlock Key (DUK) is a random binary key that:

  1. Is generated from /dev/urandom by kexec-seal-key (128 bytes — 1024 bits of entropy, i.e. a $2^{1024}$ brute-force space).
  2. Is sealed to TPM NVRAM with the LUKS DUK seal policy in tpm.md.
  3. Is added as a LUKS key slot alongside the user's Disk Recovery Key (DRK).
  4. At boot, kexec-insert-key unseals it and injects it into a minimal initrd prepended to the OS initrd. The OS kernel unlocks LUKS without prompting the user.

If the TPM refuses to unseal (PCR mismatch, TPM reset), the OS falls back to prompting for the DRK passphrase. The DRK is always a valid recovery path.


Integrity gate before sealing

When a prior TOTP/HOTP failure has set INTEGRITY_GATE_REQUIRED, gate_reseal_with_integrity_report in gui-init runs before sealing new TPM secrets and verifies:

  1. TPM is not in a reset-required state.
  2. No prior TOTP/HOTP failure is recorded (INTEGRITY_GATE_REQUIRED is unset).
  3. /boot hash verification passes.
  4. kexec.sig is valid and signed by a key in the current keyring.
  5. If HOTP is enabled: the USB security token is present.
  6. User explicitly confirms proceeding.

If any check fails, the sealing operation is aborted. This prevents new secrets from being sealed against a potentially compromised /boot. If the flag is not set, only the reset required check runs.

For the UNKNOWN_KEY scenario and correct error messaging, see ux-patterns.md.


OEM Factory Reset

oem-factory-reset re-establishes full ownership of the device in five phases:

  1. TPM reset — clears the TPM owner hierarchy, removes all NVRAM indices, and invalidates all sealed secrets.
  2. GPG key initialization — generates new keys (in-memory RSA or ECC, or on-smartcard) and configures the OpenPGP card PINs. The card PIN length is limited to 25 characters due to a firmware constraint on supported tokens (Librem Key / Nitrokey HOTP).
  3. TPM rollback counter creation — creates a new monotonic counter and stores its initial hash in /boot/kexec_rollback.txt.
  4. /boot signing — hashes and GPG-signs the initial /boot state.
  5. TOTP/HOTP and LUKS DUK sealing — the HOTP counter file is created, but TOTP/HOTP secrets are generated and sealed on the next boot; LUKS DUK sealing is performed by the user from the GUI menu.

Fail-closed design

All verification failures are fatal by default:

  • GPG signature mismatch → die (recovery shell)
  • Hash mismatch → die (recovery shell)
  • TPM counter mismatch (when CONFIG_IGNORE_ROLLBACK is not set and the rollback file exists) → die (recovery shell)
  • TOTP unseal failure → error menu (no unattended boot)
  • LUKS DUK unseal failure → OS prompts for DRK passphrase (no silent failure)

Some failures remain overridable: the TOTP/HOTP error menus offer an ignore option, and the boot options menu offers an unsafe forced boot. Overrides stay on screen in their error or warning color; for TOTP failures and invalid HOTP codes the integrity gate stays set, and ignoring a dongle communication error clears it.