Documentation
¶
Overview ¶
Package exportv1 writes the behalf.sh/export/v1 run export file per docs/export-format-v1.md: one header line with embedded JWK keys, one leaf line per receipt with the plaintext payload spliced byte-exactly, and a signed head line.
The span rule governs everything: the payload bytes handed to Append are the bytes that were signed, and they are copied into the emitted line verbatim — no re-serialization, no re-indentation. Leaf and head lines are therefore assembled by direct byte concatenation, never by re-marshaling a structure that contains the payload.
Index ¶
Constants ¶
const ( // Format is the export format string (header and head lines). Format = "behalf.sh/export/v1" // PayloadTypeReceipt is the DSSE payloadType for receipt leaves. PayloadTypeReceipt = "application/vnd.behalf.receipt+json" // PayloadTypeChainHead is the DSSE payloadType for the head line. PayloadTypeChainHead = "application/vnd.behalf.chain-head+json" )
Variables ¶
var ErrNotExport = errors.New("exportv1: not a behalf export file")
ErrNotExport marks a file that is not a behalf export at all, so a caller can say so plainly rather than reporting a parse error from line one.
Functions ¶
func ChainNext ¶
ChainNext folds one leaf hash into the chain: chain_i = SHA-256(chain_{i-1} || leaf_hash_raw_32_bytes).
func ChainStart ¶
ChainStart returns SHA-256("behalf.sh/chain/v1\n" + logOrigin).
func TokenRef ¶
TokenRef is the key form used by the header's `tokens` section: exactly the string a hop's `verification.evidence_ref` carries, `sha256:<hex>`.
Keying by the whole reference rather than by the bare digest is deliberate. A reader looks a hop's token up by the value the receipt already holds, with no string surgery in between, so there is no opportunity to reconstruct the key slightly differently on one side and miss.
Types ¶
type Export ¶
type Export struct {
LogOrigin string
Keys []HeaderKey
Leaves []Leaf
Head *Head
// Tokens is the header's delegation hop tokens, keyed by the
// `evidence_ref` the receipts carry (ENG-38). Nil when the export
// predates the section or carries no signed hops.
//
// Every entry has already been checked to digest to the key it sits
// under, so a caller may look a hop's token up by its `evidence_ref` and
// use the bytes without re-hashing. A file whose token does not match its
// address does not parse at all: substituting the evidence for a
// verification claim is the one thing this section must not permit
// silently.
Tokens map[string]string
}
Export is a parsed export file.
type Head ¶
type Head struct {
// Bytes is the exact span the head signature covers.
Bytes []byte
LogOrigin string
Count int
Chain string // hex
KeyID string
Sig []byte
}
Head is the export's trailing signed head.
type HeaderKey ¶
HeaderKey is one entry in the header's keys array: an Ed25519 public key in JWK form, keyed by its RFC 7638 thumbprint.
type Leaf ¶
type Leaf struct {
Index int
// PayloadType and Payload are what the signature covers. Payload aliases
// the line's own bytes: the exact span, never re-serialized.
PayloadType string
Payload []byte
KeyID string
Sig []byte
// LeafHash is the value the line carries, already checked against the
// payload span.
LeafHash [32]byte
}
Leaf is one receipt as it appears in an export.
type Signer ¶
type Signer struct {
Private ed25519.PrivateKey
KeyID string
}
Signer signs leaves or the head. KeyID must be the RFC 7638 thumbprint of the corresponding public key, and that key must appear in the header.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer emits one export file. Use NewWriter (writes the header), Append for each receipt in order, then Close (writes the head).
func NewWriter ¶
NewWriter writes the header line and returns a Writer whose chain is initialized to ChainStart(logOrigin). keys must contain every public key referenced by any signature in the file.
func NewWriterWithTokens ¶
func NewWriterWithTokens(w io.Writer, logOrigin string, keys []HeaderKey, tokens map[string]string) (*Writer, error)
NewWriterWithTokens is NewWriter carrying the delegation hop tokens the receipts reference (ENG-38).
tokens maps a hop's `verification.evidence_ref` — `sha256:<hex>`, see TokenRef — to that hop's compact JWS. Without it an export states that a chain was verified and carries nothing to re-verify it against: the hop signatures were checked once, at capture, by behalf's own code, and a receipt is not evidence of its own verification.
It goes in the header rather than in a new line kind or a sidecar for three reasons. The export stays one self-contained artefact, which the browser verifier and `behalf export --html` both rest on. Hops deduplicate — a run's 47 receipts typically share three hops, so the section grows with distinct hops rather than with receipts. And §2 of the format already requires unknown members to be ignored, so a verifier that predates this section reads the file exactly as before and the format string does not move.
A nil or empty map writes no `tokens` member at all, which is what keeps every existing vector byte-identical.
func (*Writer) Append ¶
Append signs payload (the exact sealed receipt bytes), splices it verbatim into a leaf line, writes the line, and folds the leaf hash into the chain.
func (*Writer) AppendSigned ¶
AppendSigned is Append for a payload that already carries its signature (the Week-2 log bridge: the emitter signed the payload at capture time and the stored envelope carries that signature — the bridge must splice the original bytes and the original signature, never re-sign). sig must be the Ed25519 signature over PAE(PayloadTypeReceipt, payload) by the header key with thumbprint keyid.
func (*Writer) Close ¶
Close writes the signed head line. The head value's exact byte span is what gets signed (PAE with the chain-head payloadType) and it is spliced verbatim into the line, same rule as leaves.
func (*Writer) LeafHashes ¶
LeafHashes returns the leaf hashes appended so far, in order.