bundle

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

bundle

ℹ BlakBox pivoted on 2026-08-07 to an industry-agnostic tender-builder appliance. This repo survives the pivot — the bundle format is directly reusable for tender delivery and model-flash bundles, and it is scheduled to grow (SPEC v2 manifest-of-files payload, lan/ OpenAPI + TLS profile, a model-bundle/ statement type). Nothing here is superseded. The plan is appliance/docs/PIVOT-PROGRAMME.md; this repo's public contracts are themselves part of the trust story.

The BlakBox bundle format — the shared contract that crosses the air gap.

Two things that must stay in lockstep:

  1. The spec (SPEC.md) — the versioned, offline-verifiable bundle + Airlock-Report format.
  2. The Go reference library — signing/verification + encryption used by every component that produces or consumes a bundle.

Why this repo is public

The format and verifier are published so customers (and their assessors) can verify BlakBox artifacts independently, without trusting us — the "verifiable transparency" property.

Consumed by:

  • Blak-Box/exporter — customer-side, produces bundles (links this module)
  • Blak-Box/appliance — on-box airlock: five Go modules link this library directly (attestd, receiver, verifier, scanner, egress), two of them via local replace directives pending the first contract tag. (Corrected 2026-08-05 — this line previously claimed the appliance "consumes by spec + verifier binary, never links this Go directly", which has not been true since the airlock modules landed. The independent-verify property still holds: the format is fully specified in SPEC.md and an assessor can implement it without this code.)

Crypto (design: docs/24-tender-airlock.md §5 in the appliance repo)

  • Envelope: in-toto v1 Statement in a DSSE v1.0.2 envelope, verified with a 2-of-2 algorithm-typed threshold.
  • Signing: ECDSA P-384 now, algorithm-agile toward ML-DSA-87 (Ed25519 fails ISM-0471).
  • Bulk: AES-256-GCM STREAM; key wrap ECDH P-384 (not X25519).
  • Built with the Go FIPS module (GOFIPS140=v1.0.0) — the only FIPS 140-3 module validated on aarch64 Linux.

Status

Core crypto implemented and tested (standard + FIPS): ECDSA-P384 sign/verify over DSSE + in-toto Statement; algorithm-typed threshold policy (phase-1 1-of ECDSA -> enforced 2-of-2); AES-256-GCM STREAM (D1) + ECDH-P384 key wrap (D4); and the ML-DSA-87 second signer (phase-2 hedge — not FIPS-validated yet, so never the sole trust path). The phase-1 path passes strict fips140=only; ML-DSA runs under the FIPS build but outside strict mode by design.

The bundle CLI is BUILT by this repo's release workflow (FIPS-built, provenance-asserted: bundle version reports fips140:v1.0.0; SHA256SUMS attached per tag-triggered release) — note no v* tag has been pushed since the workflow landed, so no released binaries exist yet; the appliance installs a locally-built copy at factory time. Still to land: FastCDC chunk store, published KAT test vectors, and the official in-toto/attestation/go/v1 Statement type. The Ed25519 -> ECDSA P-384 update-chain migration consumes this library once for the whole product.

Working on this repo

One-time setup per clone, matching the gate discipline in the other Blak-Box repos:

pre-commit install

That installs the hooks in .pre-commit-config.yaml: the shared file-shape checks (byte-identical to the appliance repo's, so the language-agnostic half of the gate set is the same everywhere), plus gofmt -l and go vet ./.... Together they run in about four seconds warm.

They deliberately mirror the fast half of ci.yml and stop there. Build, go test -race and the FIPS and strict-FIPS tiers stay in CI: a commit hook that takes a minute is a commit hook people learn to skip with --no-verify, and a gate that is routinely bypassed is worse than no gate because it still reads as protection.

Documentation

Overview

Package bundle is the BlakBox bundle-format reference library: signing, verification, and (later) encryption for the signed, offline-verifiable artifacts that cross the air gap.

This file implements the phase-1 signature primitive: ECDSA P-384 over the DSSE Pre-Authentication Encoding (PAE), hashed with SHA-384 (decision D5). The signer/verifier satisfy go-securesystemslib/dsse's Signer/Verifier interfaces so the DSSE library owns the envelope + PAE and we own only the key math. ML-DSA-87 arrives as a second, algorithm-typed signer (D3/phase 2).

Index

Constants

View Source
const (
	// StatementType is the in-toto v1 Statement `_type`.
	StatementType = "https://in-toto.io/Statement/v1"

	// PayloadType is the DSSE payloadType for an in-toto Statement.
	PayloadType = "application/vnd.in-toto+json"

	// BundlePredicateType is BlakBox's bundle predicate.
	BundlePredicateType = "application/vnd.blakbox.bundle+json"

	// SourceBatchPredicateType is the desktop-connector source-batch predicate
	// (SPEC §4): a signed batch of customer files walked from a connected
	// source, bound for the box's evidence corpus. A distinct type — never a
	// reuse of the export predicate — because the two are different ingress
	// trust classes and the verifier's exact-match pin is what stops
	// cross-type replay.
	SourceBatchPredicateType = "application/vnd.blakbox.source-batch+json"

	// ModelBundlePredicateType is the factory-side model bundle (SPEC §5): the
	// set of model weights an appliance is imaged with, signed by the factory and
	// verified on the box before use.
	//
	// THE GAP IT CLOSES. scripts/fetch_models.sh in the appliance already builds
	// this artifact — it downloads weights on a CONNECTED machine, writes a
	// CHECKSUMS file, and rsyncs the tree to /opt/blakbox/models on the air-gapped
	// box, whose instruction is `sha256sum -c CHECKSUMS`. That is INTEGRITY
	// WITHOUT AUTHENTICITY: a checksum file is exactly as trustworthy as the
	// channel it arrived on, and whoever can alter the rsync can alter the weights
	// and the checksums together. Signing the manifest makes the box able to tell
	// the factory's weights from someone else's.
	//
	// A distinct type for the same reason SourceBatchPredicateType is distinct:
	// weights and customer evidence are different trust classes, and the
	// verifier's exact-match pin is what stops one being replayed as the other.
	ModelBundlePredicateType = "application/vnd.blakbox.model-bundle+json"
)
View Source
const (
	// SegmentSize is the plaintext size of each STREAM segment (D1): 1 MiB.
	SegmentSize = 1 << 20
	// CEKSize is the length of a content-encryption key (AES-256).
	CEKSize = 32
)

Variables

View Source
var ErrWrongCurve = errors.New("bundle: key is not ECDSA P-384")

ErrWrongCurve is returned when a key is not on the P-384 curve.

Functions

func DecryptStream

func DecryptStream(w io.Writer, r io.Reader, cek, streamAAD []byte) error

DecryptStream reverses EncryptStream, writing recovered plaintext to w. It rejects any tamper, reorder, duplication, truncation, extension, or cross-stream splice.

func EncryptStream

func EncryptStream(w io.Writer, r io.Reader, cek, streamAAD []byte) error

EncryptStream reads plaintext from r and writes the AES-256-GCM STREAM to w.

func GenerateCEK

func GenerateCEK() ([]byte, error)

GenerateCEK returns a fresh random 256-bit content-encryption key.

func KeyFingerprint

func KeyFingerprint(pub crypto.PublicKey) (string, error)

KeyFingerprint returns the stable identity fingerprint of a public key — hex(SHA-256(SPKI DER)) for stdlib keys, hex(SHA-256(raw bytes)) for ML-DSA. It is the exact value the policy uses to match signatures back to pinned anchors (decision D3) and the value written to a keypair's .fingerprint sidecar, so an out-of-band fingerprint comparison and the on-box trust decision agree on one identity.

This is the exported entry point for the same computation the package uses internally; consumers (the CLI, the exporter, signed egress) MUST derive a key's identity through this function so every component agrees.

func SignStatement

func SignStatement(priv *ecdsa.PrivateKey, st *Statement) (*dsse.Envelope, error)

SignStatement marshals st and signs it into a DSSE envelope with ECDSA P-384 (phase-1 signing path, decision D5). The DSSE library handles the envelope and Pre-Authentication Encoding; we supply only the key math.

func SignStatementHybrid

func SignStatementHybrid(ecdsaPriv *ecdsa.PrivateKey, mldsaPriv *mldsa.PrivateKey, st *Statement) (*dsse.Envelope, error)

SignStatementHybrid signs st with BOTH ECDSA-P384 and ML-DSA-87, producing a 2-of-2 DSSE envelope — the post-2030 hybrid-PQC path. Verify it with a Policy in Enforced2of2 mode pinned to the matching ECDSA and ML-DSA anchors.

func UnwrapCEK

func UnwrapCEK(recipient *ecdh.PrivateKey, stanza *WrapStanza) ([]byte, error)

UnwrapCEK recovers the CEK from a stanza using the recipient's static ECDH P-384 private key.

Types

type Algorithm

type Algorithm string

Algorithm identifies a signature algorithm for algorithm-typed threshold enforcement (decision D3).

const (
	AlgECDSAP384 Algorithm = "ECDSA-P384"
	AlgMLDSA87   Algorithm = "ML-DSA-87"
)

type Anchor

type Anchor struct {
	Algorithm Algorithm
	Public    crypto.PublicKey
}

Anchor is a pinned trust anchor: an algorithm tag plus its public key. The policy matches signatures to anchors by PUBLIC KEY, never by the envelope's advisory keyid (decision D3).

type Mode

type Mode int

Mode is the threshold-enforcement mode (decision D3).

const (
	// Phase1 accepts the bundle on at least one valid ECDSA-P384 signature from
	// a pinned anchor — the only FIPS-validated signature path today.
	Phase1 Mode = iota
	// Enforced2of2 requires one valid ECDSA-P384 AND one valid ML-DSA-87
	// signature, each from a distinct pinned anchor (post-2030; hybrid PQC).
	Enforced2of2
)

type Policy

type Policy struct {
	Anchors []Anchor
	Mode    Mode
}

Policy is a set of pinned anchors plus a threshold mode. It is the authoritative verification path; the package-level VerifyStatement is a Phase1 convenience wrapper over it.

func (*Policy) VerifyStatement

func (p *Policy) VerifyStatement(env *dsse.Envelope) (*Statement, error)

VerifyStatement verifies env against the policy and returns the in-toto Statement. It (1) verifies signatures cryptographically, (2) maps each accepted signature back to its pinned anchor by public key, then (3) enforces the algorithm-typed threshold for the policy's Mode. A signature from a key that is not pinned never counts, and one anchor cannot satisfy the threshold twice (de-duplicated by public key).

type Statement

type Statement struct {
	Type          string         `json:"_type"`
	Subject       []Subject      `json:"subject"`
	PredicateType string         `json:"predicateType"`
	Predicate     map[string]any `json:"predicate,omitempty"`
}

Statement is an in-toto v1 Statement.

func NewStatement

func NewStatement(predicateType string, predicate map[string]any, subjects ...Subject) *Statement

NewStatement builds a Statement of the given predicate type over the subjects.

func VerifyStatement

func VerifyStatement(anchors []*ecdsa.PublicKey, env *dsse.Envelope) (*Statement, error)

VerifyStatement is a Phase1 convenience wrapper: it verifies env against the supplied ECDSA-P384 anchor keys, accepting on at least one valid signature, and returns the decoded in-toto Statement. For the full algorithm-typed threshold (including enforced 2-of-2 with ML-DSA), construct a Policy directly and call Policy.VerifyStatement.

type Subject

type Subject struct {
	Name   string            `json:"name"`
	Digest map[string]string `json:"digest"`
}

Subject is a signed-over resource: a name plus a digest set (algorithm -> lowercase hex). Per the design, subjects carry sha256 (+ sha384 for 2030 alignment).

type WrapStanza

type WrapStanza struct {
	// EphemeralPublic is the sender's ephemeral ECDH P-384 public key
	// (uncompressed point, crypto/ecdh encoding).
	EphemeralPublic []byte
	// Wrapped is AES-256-GCM(nonce||ciphertext||tag) of the CEK.
	Wrapped []byte
}

WrapStanza is one recipient's wrapped copy of the CEK.

func WrapCEK

func WrapCEK(recipient *ecdh.PublicKey, cek []byte) (*WrapStanza, error)

WrapCEK wraps cek to a recipient's static ECDH P-384 public key.

Directories

Path Synopsis
cmd
bundle command
Command bundle is the CLI bridge to the BlakBox bundle crypto core.
Command bundle is the CLI bridge to the BlakBox bundle crypto core.
Package modelbundle is the predicate the factory signs over an appliance's model set, and that the box verifies before loading any of it.
Package modelbundle is the predicate the factory signs over an appliance's model set, and that the box verifies before loading any of it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL