A Secure-Boot-compatible, UEFI-native pre-boot trust broker for TCG Opal
self-encrypting drives (SEDs), written in Go on TamaGo.
It boots from the drive's Shadow MBR, unlocks the SED before the OS starts, disables
the Shadow MBR, and hands off cleanly to Windows, Linux, or a customer-controlled EFI
application — without ever leaving UEFI boot services (no ExitBootServices, so the
Windows Boot Manager handoff stays clean, unlike Linux-kernel PBAs).
Security-critical pre-boot product. It fails closed on every authentication, unlock, verification, or policy error and never boots an untrusted or revoked image. Development follows a documented secure-development lifecycle for CRA / ISO 27001 / SSDF / SLSA readiness. Read
CLAUDE.mdandAGENTS.mdbefore contributing.
Status · Features · How it works · Project structure · Build & run · Configuration · Testing · Security · Documentation · Contributing · License
The core PBA is feature-complete and validated in QEMU/OVMF CI; hardware bring-up is in progress.
| Capability | State |
|---|---|
Opal SED unlock (discovery → auth → unlock → MBRDone) |
✅ Working, proven against real hardware and the Linux sed-opal wire format |
| UEFI Storage Security transport | ✅ Working |
Secure Boot detection & require_secure_boot enforcement |
✅ Working |
| Compiled-in fail-closed boot policy | ✅ Working |
Image validation — firmware / pba / pba-override modes |
✅ Working |
| Chainload to Windows / Linux / custom EFI | ✅ Working (virtual) |
| Real on-hardware chainload, recovery, per-drive matrix | 🚧 In progress |
| Real user/device authentication (vs. compiled-in test PIN) | 📋 Planned |
| Signed, provenanced release artifact | 📋 Planned |
See the issues and milestones for current work.
- UEFI-native, boot-services-resident PBA on TamaGo — stays in UEFI through the
handoff; never calls
ExitBootServices(ADR-0001/0002). - TCG Opal SED unlock — Level-0 Discovery → authenticated Locking-SP session
(Admin1, PIN as host challenge) → clear the global locking range → set
MBRDone(disable the Shadow MBR) → end session. Byte-faithful TCG wire format. - UEFI Storage Security transport —
EFI_STORAGE_SECURITY_COMMAND_PROTOCOL(IF-SEND/IF-RECV), enumerating every drive and selecting the locked SED with an active Shadow MBR. Opal protocol logic is cleanly separated from the transport (ADR-0004). - Secure Boot aware & enforcing — detects
SecureBoot/SetupModeand, when the policy setsrequire_secure_boot, fails closed unless the firmware is enforcing (ADR-0003). Never treats Secure Boot on and off as equivalent. - Compiled-in boot policy (fail-closed JSON) — see Configuration.
- Three second-stage validation modes (per boot entry):
firmware— the PBA loads the target and lets firmware Secure Boot validate it (the Windows Boot Manager path — we don't re-implement Windows validation).pba— the PBA validates the image itself: full Authenticode PE hashing + PKCS#7 chain to an embedded Microsoft trust store, withdbxrevocation (hash and cert). Time-independent, exactly like firmware. The verified buffer is the executed buffer (no verify-then-load gap).pba-override—pbavalidation plus a SHIM-style Secure Boot override so an image firmwaredbdoes not trust still loads (a platform trusting only our PBA key). Compiled only with-tags trustbroker; diverges PCR 7, so it is scoped away from BitLocker/measured-boot targets (ADR-0012/0013).
- Embedded Secure Boot trust store — Windows-only by default (Windows Production PCA
2011 + Windows UEFI CA 2023);
-tags trustfulladds the third-party Microsoft UEFI CAs for shim/GRUB/Linux. Byte-identical Microsoft materials + the realdbx(ADR-0007). - Single-hop trust broker (ADR-0010) — validates the one image it chainloads, then
transfers control; the next stage brokers its own downstream trust (shim/MOK for Linux,
firmware
dbfor Windows). No boot-services wrapping. - Fail-closed by construction — any error (policy parse, Secure Boot gate, transport,
discovery, unlock, verification, load) terminates via the policy
on_erroraction; control never returns to the firmware boot order. PINs are zeroized once and never logged. - Fully virtual test path — unit + fuzz tests, an EDK2
MockOpalDxedriver, and QEMU matrices run on every PR in CI, with no hardware.
- Real hardware bring-up — SED unlock is validated on real NVMe Opal hardware; remaining: on-hardware chainload to Windows/Linux, PSID reset/recovery, and a per-drive compatibility matrix.
- Real user/device authentication (ADR-0011, proposed) — replace the compiled-in test
PIN with a pluggable
credential.Source(console prompt, key file, TPM-PCR-sealed, YubiKey, Tang/Clevis, …) and MFA composition. This is the path to a shippable unlock UX. - Robust drive targeting (#81) — a policy/config-specified drive identity (NVMe serial / WWN) to replace the interim "locked SED with a Shadow MBR" selection heuristic.
- NVMe PassThru transport — a raw-NVMe carrier (prototyped and HW-validated in the go-boot fork) for firmware whose Storage Security mediation is unreliable; not yet in the shipping tree.
- Multi-entry boot selection / fallback — today the policy selects the first entry; availability-based fallback is not yet implemented.
- QEMU virtual SED device model (deferred) — faithful Shadow-MBR block visibility, to test the locked→revealed disk transition virtually (today's mock exercises the protocol, not real block gating).
- Reproducible release build + SBOM/provenance/signing —
release.ymlscaffolding exists; the signed, provenanced release artifact is still TODO.
The PBA runs as a UEFI application launched from the drive's Shadow MBR. Its boot flow:
detect Secure Boot state
→ parse the compiled-in policy (fail closed on any error)
→ enforce require_secure_boot (fail closed if required but firmware isn't enforcing)
→ unlock the SED: discover → authenticate (Admin1) → clear the locking range → MBRDone
→ select the boot entry (first entry today)
→ chainload it, validated per the entry's mode (firmware | pba | pba-override)
→ on ANY failure: halt / shutdown / reboot per policy — never return to firmware boot order
Two architectural rules keep it auditable: the Opal protocol layer depends only on an abstract transport interface (never on UEFI), and the PBA is a single-hop trust broker — it validates only the one image it hands control to, then that image brokers its own downstream trust.
cmd/pba/ UEFI app entry point + boot flow: main.go, SED unlock (sedunlock.go),
chainload + validation modes (override_trustbroker.go / override_notbuilt.go),
console sink selection (console_*.go), host build stub (main_stub.go)
internal/
opal/ TCG Opal protocol: Level-0 Discovery, sessions, methods, tokens, packets,
UIDs — transport-agnostic; MockTPer for tests
transport/ UEFI Storage Security carrier (uefi_tamago.go) + !tamago host stub
policy/ compiled-in JSON boot policy: schema + fail-closed parse (policy.go),
the policy_*.json variants, and embed_*.go (build tag → which JSON)
imageverify/ Authenticode PE hashing + PKCS#7 chain + dbx verification
truststore/ embedded Microsoft db CAs + real dbx (build-tag variants);
materials under materials/{db,dbx} with PROVENANCE.md
secureboot/ reads firmware SecureBoot / SetupMode state (+ host stub)
tbstub/ the Security2-override trust-broker asm stub used by pba-override
test/
qemu/ QEMU/OVMF harness scripts + the test matrices
edk2-mock-opal/ the MockOpalDxe EDK2 driver (virtual SED for integration tests)
fixtures/ test images, keys, and helpers
docs/ product / security / compliance / architecture (ADRs) / development docs
evidence/ CRA/ISO compliance evidence (SBOM, threat model, test/fuzz/SAST reports,
provenance, security-review records)
Taskfile.yml the build system (go-task; there is no Makefile)
Built with go-task (no Makefile); TamaGo is pinned and auto-installed.
task setup # install the pinned TamaGo toolchain + resolve deps (one-time)
task build # build bin/trusted-pba.efi
task run # boot in QEMU/OVMF and assert the chainload markers
task check # local CI: lint + unit tests + the QEMU matricesThe PBA takes no runtime configuration — no config file on disk, no flags, no environment variables. Everything is fixed at build time, in two layers: a compiled-in JSON boot policy and a set of build tags. This is deliberate for a pre-boot binary: the policy is part of the signed, measured artifact and cannot be tampered with at runtime.
One JSON policy is embedded into the binary via //go:embed (see
internal/policy/). It is parsed fail-closed: unknown fields,
trailing data, or an invalid value yield an error and no usable policy, so the PBA
refuses to boot rather than guessing.
| Field | Type | Default | Meaning |
|---|---|---|---|
require_secure_boot |
bool | false |
If true, fail closed unless firmware Secure Boot is enforcing (SecureBoot=1 && SetupMode=0). |
sed_unlock |
"required" | "none" |
required (absent ⇒ required) |
Whether the PBA must unlock an Opal SED before chainload. "none" is an explicit, logged decision for non-SED machines — never an implicit fallback. |
sed_credential |
object | — | Where the unlock credential comes from. Required when sed_unlock is required; must be absent when none. Fields: source ("policy-pin" | "console" | "keyfile"), pin (string; policy-pin only — a compiled-in Admin1 credential, redacts as [redacted], zeroized after use; a debug source rejected by the release gate), path (string; keyfile only — ESP-relative path to the key file), derive ("raw" default | "sedutil-pbkdf2"). console prompts the operator at the pre-boot console (no pin/path). keyfile is low-assurance: the file content is the credential in cleartext, so its security is only the confidentiality of its location — on the ESP it does not defend a physical/evil-maid attacker (who can read it); it is meaningful only on separate removable media or as one factor in MFA. Bytes are used exactly as stored (no trimming). The drive authenticates the credential, so a wrong/tampered keyfile fails closed (never a bypass). |
on_error |
"halt" | "shutdown" | "reboot" |
halt |
Terminal fail-closed action. None of them return control to the firmware boot order; reboot re-runs the PBA from the start. |
entries |
array (≥1, required) | — | Candidate boot targets. Today only the first entry is used (availability-based selection is not yet implemented). |
entries[].name |
string | — | Human-readable entry name (required). |
entries[].path |
string | — | ESP-relative image path, e.g. EFI/TEST/TESTAPP.EFI (required). |
entries[].validation |
"firmware" | "pba" | "pba-override" |
— | How the image is validated before it is started — see validation modes. pba-override requires the trustbroker build tag or it fails closed at chainload. |
Example (the default development policy):
{
"require_secure_boot": false,
"sed_unlock": "none",
"entries": [
{ "name": "test-fixture", "path": "EFI/TEST/TESTAPP.EFI", "validation": "firmware" }
]
}Build tags select which policy JSON and trust store are embedded, the console output, and
whether the override path is compiled in. The default build (no tags) embeds
policy.json, the Windows-only trust store, and dual console output. TamaGo also needs
the base linker tags linkcpuinit,linkramsize,linkramstart,linkprintk (set automatically by
the Taskfile).
| Tag | Effect |
|---|---|
| (none) | Default: embed policy.json, Windows-only trust store, ConOut + serial console. |
sedtest |
Embed policy_sed.json (sed_unlock: required + a test PIN) — mock-Opal unlock matrix. |
pbatest |
Embed policy_pba.json (validation pba) and substitute a test trust store (test CA + crafted dbx) — PBA-validation matrix. |
policytest |
Embed policy_require_sb.json (require_secure_boot: true) — Secure Boot enforcement case. |
overridetest |
Embed policy_overridetest.json (validation pba-override); build with overridetest,pbatest,trustbroker. |
trustfull |
Add the third-party Microsoft UEFI CAs (shim/GRUB/Linux) on top of the two Windows CAs. Default is Windows-only. |
trustbroker |
Compile in the pba-override Security2-override path (internal/tbstub). Without it, a pba-override entry fails closed. |
serialonly |
Console output to COM1 serial only (headless/CI). Default fans out to both UEFI ConOut and COM1. |
The policy-embed tags are mutually exclusive by construction — exactly one policy JSON is ever embedded; an invalid combination is a compile error, not a silent wrong policy.
Every feature has a virtual test path; the suites below run in CI on every PR (CI fails closed), with no hardware:
| Suite | What it proves |
|---|---|
task test / task lint / fuzz |
Opal codec, policy parse, Authenticode verify, Secure Boot state — with fuzzing and fail-closed negatives |
task mock-opal-matrix |
End-to-end unlock → MBRDone → chainload against the EDK2 MockOpalDxe SED, plus auth-fail / MBRDone-fail / partial-unlock / no-driver fail-closed cases |
task sb-matrix |
Secure Boot: off, enforcing-signed, unsigned-rejected, untrusted-key-rejected, dbx-hash-revoked |
task pba-matrix |
pba-validation: accept a trusted image, reject unsigned, reject dbx-revoked |
task override-pcr |
pba-override: confirms the PCR-7 divergence under a vTPM (why it is scoped away from BitLocker) |
Real hardware, real Windows boot, and the faithful Shadow-MBR reveal are not covered
virtually — see docs/test-tooling-plan.md.
- Fail-closed always. Any authentication, unlock, verification, or policy error
terminates per
on_errorand never returns control to the firmware boot order. The non-negotiable rules are inCLAUDE.md. - Secure Boot is not optional-equivalent. The PBA never treats Secure Boot enabled and disabled as the same, and never silently falls back from secure to insecure boot.
- No secrets in logs. PINs, keys, and Opal session material are never logged; the PIN
type renders as
[redacted]and is zeroized after use. - Do not deploy the compiled-in test PIN. The MVP embeds a test credential in the policy; it is a placeholder, extractable from the image, and must be replaced by real authentication (ADR-0011) before any production use.
- Threat model & risk assessment:
docs/security/threat-model.mdanddocs/compliance/risk-assessment.md.
Reporting a vulnerability: please don't open a public issue. Use GitHub's Report a vulnerability (Security tab) or email security@9elements.com. See
SECURITY.mdfor scope and process.
CLAUDE.md— working agreement, the four rules, non-negotiable security rulesdocs/trusted-pba-plan.md— product plan, MVP, success criteriadocs/test-tooling-plan.md— virtual test toolingdocs/architecture/adr/— architecture decision records (ADR-0001…0013)docs/compliance-and-secure-development-baseline.md— the dev/compliance processdocs/— product / security / compliance / architecture / development docs
Every change starts as an issue, is worked on a feat/·fix/·chore/·docs/ branch,
and is opened as a PR against main (protected — PR only, no direct/force push). CI must
pass (it fails closed), and the change is reviewed before merge. Security-critical and
release changes require human approval and, where applicable, an ADR.
- Sign off every commit with
git commit -s(DCOSigned-off-by:trailer) — no exceptions, including amends and squashes:Signed-off-by: Your Name <you@example.com> - See
docs/development/branching.mdand the compliance baseline §7 (SDLC) / §23 (change management).
Not yet finalized. A LICENSE file will be added; until then, all rights are reserved by
the project owners. Contact the maintainers before redistributing.