Skip to main content

pqc-bootloader

PQC Native ML-DSA-65 SHA3-256 License: Apache-2.0 Version

PQC-native signed-boot framework for AI appliances. Edge inference servers deployed in hospitals, factories, and military installations have 10-15 year operational lifespans. Firmware signed today with RSA-2048 or ECDSA-P256 is a Harvest-Now-Decrypt-Later target: a cryptographically relevant quantum computer (CRQC) in the 2030-2035 window can forge a signature on a malicious firmware image and push it into a fleet of appliances that still believe the original root-of-trust is valid. pqc-bootloader is a drop-in cryptographic layer that replaces RSA_verify() in your bootloader with ML_DSA_verify(), ships a manufacturer key-ring, enforces non-rollback via an update chain, and produces a TPM-style measured-boot PCR — so an appliance built today still has a defensible root-of-trust in 2040.

Install

pip install pqc-bootloader

Quick start

from quantumshield.identity.agent import AgentIdentity
from pqc_bootloader import (
    FirmwareImage, FirmwareMetadata, TargetDevice,
    FirmwareSigner, FirmwareVerifier,
    KeyRing, MeasuredBoot, BootStage, BootAttestationLog,
)

# --- manufacturer ---
mfr = AgentIdentity.create("acme-appliance-vendor")
signer = FirmwareSigner(mfr)

metadata = FirmwareMetadata(
    name="acme-inference-os", version="1.2.3",
    target=TargetDevice.AI_INFERENCE_APPLIANCE,
)
firmware = FirmwareImage.from_bytes(metadata, open("firmware.bin", "rb").read())
signed = signer.sign(firmware)

# --- appliance ---
ring = KeyRing()
ring.add(mfr.signing_keypair.public_key.hex(),
         mfr.signing_keypair.algorithm.value, "Acme Inc.")

result = FirmwareVerifier.verify(signed,
                                 actual_bytes=firmware.image_bytes,
                                 key_ring=ring)
assert result.valid

mb = MeasuredBoot()
mb.extend(BootStage.BOOTLOADER, open("/boot/bootloader.bin", "rb").read())
mb.extend(BootStage.KERNEL,     open("/boot/vmlinuz", "rb").read())
mb.extend(BootStage.INITRD,     open("/boot/initrd.img", "rb").read())

log = BootAttestationLog()
log.log_accept(firmware.metadata.name, firmware.metadata.version,
               firmware.image_hash, pcr_value_after=mb.pcr_value)

Architecture

  +------------------+       ML-DSA-65 sign            +--------------------+
  |   Manufacturer   |-------------------------------->|  SignedFirmware    |
  |  AgentIdentity   |   (metadata + SHA3-256 hash)    |  .to_dict() bytes  |
  +------------------+                                 +--------------------+
                                                                 |
                                                                 v
                                        +----------------------------------+
                                        |   Distribution (OTA / USB / CDN) |
                                        +----------------------------------+
                                                                 |
                                                                 v
  +----------------+    reads    +-----------+   check    +-------------------+
  |   Boot ROM     |------------>| KeyRing   |<-----------| manufacturer_key_id|
  |  (U-Boot/GRUB  |             | allow-list|            +-------------------+
  |   fork)        |             +-----------+                    |
  +----------------+                   |                          |
                                       v                          v
                             +------------------+     +-----------------------+
                             | FirmwareVerifier |<----| SHA3-256 hash recompute|
                             |  (ML_DSA_verify) |     +-----------------------+
                             +------------------+
                                       |
                        +--------------+--------------+
                        |                             |
                  ACCEPT  v                           v  REJECT
             +-------------------+          +-------------------+
             |  MeasuredBoot     |          | BootAttestationLog|
             |  PCR chain        |          |  log_reject(...)  |
             |  bootloader-kernel|          |  -> halt/fallback |
             |  -initrd-userspace|          +-------------------+
             +-------------------+
                        |
                        v
             +-------------------+
             | BootAttestationLog|
             |  log_accept(...,  |
             |  pcr_value_after) |
             +-------------------+

Cryptography

Primitive Algorithm Role
Signature ML-DSA-65 (FIPS 204) Firmware manifest signatures
Hash SHA3-256 (FIPS 202) Firmware image hash + PCR extend
Identity quantumshield DID Manufacturer + device identifiers
Key fingerprint SHA3-256 of public key manufacturer_key_id in KeyRing

The manufacturer signs a canonical manifest (JSON, sort_keys, no whitespace) covering the firmware metadata, SHA3-256 image hash, and image size — not the image bytes themselves. The bootloader recomputes the manifest from the delivered blob, then verifies the ML-DSA signature over that manifest. This means the signature is small and constant-size regardless of firmware size (which can be hundreds of MB for inference OSes with bundled model weights).

Threat model

Threat Mitigation
Firmware HNDL ML-DSA-65 signatures are quantum-safe; CRQC cannot forge them
Rogue update (signed by attacker with their own key) KeyRing allow-list; untrusted manufacturer_key_id rejected
Rollback attack (legit older firmware re-deployed to re-introduce CVE) UpdateChain.add() blocks when new.version < prev.version unless allow_rollback=True
Stolen manufacturer key KeyRing.revoke(key_id, reason) marks entry; is_trusted() returns False. Rotate to a new manufacturer key and re-sign in-field firmware.
Measured-boot tamper MeasuredBoot.extend() chains SHA3(prev_pcr || measurement); any swap of bootloader/kernel/initrd/userspace yields a different final PCR, detectable by remote attestation
Image hash substitution (manifest signed, but delivered image is different) FirmwareVerifier.verify(signed, actual_bytes=...) recomputes SHA3-256 over the delivered blob and rejects on mismatch
Manifest-only replay (copy metadata from one device's firmware to another) target + min_hardware_revision fields in FirmwareMetadata are part of the signed manifest

Key-ring lifecycle

from pqc_bootloader import KeyRing

ring = KeyRing()

# 1. provisioning (at factory burn-in)
entry = ring.add(
    public_key_hex=mfr_pubkey_hex,
    algorithm="ML-DSA-65",
    manufacturer="Acme Appliances Inc.",
    role="firmware-signer",
)
ring.add(supplier_pubkey_hex, "ML-DSA-65", "Contoso Systems")  # multi-vendor supply chain

# 2. check at boot
if ring.is_trusted(signed.manufacturer_key_id):
    ...

# 3. revocation (e.g. HSM compromise disclosed)
ring.revoke(entry.key_id, reason="Acme HSM compromise CVE-2032-00001")

# 4. export for audit / mirroring
print(ring.export_json())

The key-ring is designed to live in OTP / fuses or in a sealed TPM NV-index. Revocation entries persist; a revoked key is never re-trusted — rotate to a fresh key and re-sign in-field firmware instead.

Integration guide

pqc-bootloader is a cryptographic library. Real integration involves forking one of the classical signed-boot stacks:

Stack What to replace
U-Boot FIT_SIGNATURE_ALGO hook: swap rsa,sha256 for a custom mldsa,sha3-256 that shells out to a small C binding around FirmwareVerifier.verify. Pin the manufacturer public key in u-boot.dtb.
GRUB 2 Replace grub-pgp verifier with a PQC verifier module; the KeyRing exports a GPG-compatible JSON that your module parses.
coreboot Vboot v2: replace RSA2048EXP3 kernel vboot key with an ML-DSA-65 key; update firmware/2lib/2rsa.c signature-verify call-site.
UEFI Secure Boot Add ML-DSA-65 as an allowed signature algorithm in db; bootloader consumes pqc-bootloader output envelopes.

In all cases the library gives you (a) the wire-format (SignedFirmware.to_dict() / from_dict), (b) the canonical manifest bytes (FirmwareImage.canonical_manifest_bytes), and (c) the cryptographic primitives (via quantumshield.core.signatures.verify). The bootloader-specific work is wiring these into the existing verify call-site.

API reference

FirmwareMetadata

Dataclass. name, version, target (TargetDevice enum), plus optional kernel_version, architecture, build_id, release_notes_url, min_hardware_revision, security_level.

FirmwareImage

  • FirmwareImage.from_bytes(metadata, data) -> FirmwareImage
  • FirmwareImage.from_file(metadata, path) -> FirmwareImage
  • FirmwareImage.hash_bytes(data) -> str — SHA3-256 hex
  • firmware.canonical_manifest_bytes() -> bytes — signed payload
  • firmware.to_dict(include_image=False) -> dict

FirmwareSigner

  • FirmwareSigner(identity) — construct from a quantumshield.AgentIdentity
  • signer.key_id -> str — SHA3-256 of public key
  • signer.sign(firmware, previous_firmware_hash="") -> SignedFirmware

FirmwareVerifier

  • FirmwareVerifier.verify(signed, actual_bytes=None, key_ring=None) -> VerificationResult
    • actual_bytes: if supplied, recomputes SHA3-256 and checks against signed.firmware.image_hash
    • key_ring: if supplied, refuses untrusted manufacturer_key_id
  • FirmwareVerifier.verify_or_raise(...) — same but raises FirmwareVerificationError

VerificationResult fields: valid, signature_valid, hash_consistent, key_trusted, signer_did, firmware_name, error.

KeyRing

  • ring.add(public_key_hex, algorithm, manufacturer, role="firmware-signer") -> KeyRingEntry
  • ring.revoke(key_id, reason)
  • ring.get(key_id) -> KeyRingEntry (raises UnknownKeyError)
  • ring.is_trusted(key_id) -> bool
  • ring.list_entries() -> list[KeyRingEntry]
  • ring.export_json() -> str
  • KeyRing.fingerprint(public_key_hex) -> str

UpdateChain

  • chain.add(signed, allow_rollback=False) — raises UpdateChainError / FirmwareRollbackError
  • chain.current() -> SignedFirmware | None
  • chain.verify_chain() -> tuple[bool, list[str]]

MeasuredBoot

  • mb.extend(stage, content) -> str — returns new PCR hex
  • mb.reset()
  • mb.pcr_value: str / mb.measurements: list[PCRMeasurement]

BootStage enum: ROM | BOOTLOADER | KERNEL | INITRD | USERSPACE | MODEL_WEIGHTS.

BootAttestationLog

  • log.log_accept(firmware_name, firmware_version, firmware_hash, reason="", device_id="", pcr_value_after="")
  • log.log_reject(firmware_name, firmware_version, firmware_hash, reason, device_id="")
  • log.entries(limit=100, decision=None) -> list[BootAttemptEntry]
  • log.export_json() -> str

Exceptions

BootloaderError > {FirmwareVerificationError, UnknownKeyError, UpdateChainError, MeasuredBootError, KeyRingError}, plus FirmwareRollbackError(UpdateChainError).

Why PQC for bootloaders

  • Deployment lifespan vs. quantum timeline. NIST expects ML-DSA migration mandatory for federal signed firmware by 2030-2033. Medical imaging systems, factory PLCs, and military embedded platforms built today will still be in service in 2038-2040. Signing those with RSA/ECDSA is a shipped-in-vault HNDL target.
  • One-shot root of trust. Unlike TLS, bootloader keys usually can't be rotated over the air — they're burned into fuses. A bootloader signed with a classical key you can't rotate is a permanent liability.
  • Supply-chain blast radius. A forged firmware signature doesn't compromise one session; it owns the device for its operational life. An adversary harvesting today's signed update and forging it at Q-day can replace the kernel on every deployed unit at once.
  • Measured boot is orthogonal to signing. Even with PQC signatures, an attacker who tampers with the kernel after verify-and-load is caught by the PCR chain — which remote attestation consumers (RA verifiers, TEE attestors) can validate.

Examples

License

Apache-2.0. See LICENSE.

Release files for pqc-bootloader 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pqc-bootloader 0.1.0
File Size Uploaded
pqc_bootloader-0.1.0.tar.gz 20.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pqc-bootloader 0.1.0
File Interpreter ABI Platform
pqc_bootloader-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.3 kB

Release files / pqc_bootloader-0.1.0.tar.gz

Download URL pqc_bootloader-0.1.0.tar.gz
Size 20.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6ed1aec404aee9698912fe7181cf0efdebb44adbcf1698663db90aa4cda19784
BLAKE2b-256 checksum
How to use checksums
2a6c3f9c440d01d233af007a7d7e01720cff286ee670e3e15349fb1e08201ce3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / pqc_bootloader-0.1.0-py3-none-any.whl

Download URL pqc_bootloader-0.1.0-py3-none-any.whl
Size 18.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9e6f58026d55a5a3053f90833af7e45b63a9e6c3b8f1996e57ee65b79f5e1dbc
BLAKE2b-256 checksum
How to use checksums
6861ae8709ed026e5b78441405ce1719ed1061d3966e1db68f4812adf3fd42a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page