Help us shape the future of Go! Take the 2026 Go Developer Survey

anchorobj

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package anchorobj is the part of an object-store audit anchor that does not depend on any storage SDK: the layout of the objects that hold signed tree heads, their canonical body, the checks a Reader makes on an object it reads back, the equivocation check, and the tripwire for run IDs that look like personal data. The anchors in anchor/awss3 and anchor/gcs are built on it, so that a head is named, encoded and checked the same way whichever store holds it.

Experimental: the package is shared by the anchor modules and its API may change before 1.0 (docs/trust/stability.md). It is not an anchor by itself and has no I/O.

What stays per backend

A Layout names the backend: its object Format and the tag that seeds the partition hash. Both must differ between backends, so that an object of one anchor can never validate as an object of another and a partition name of one is never a partition name of another. Each backend also supplies its own error values (Layout.ErrObject and Layout.ErrBadSignature, and the equivocation error passed to CheckEquivocation), so errors.Is against a backend's own errors, and the text of its messages, are those of the backend.

The layout

Every anchored head is one object, and its name is derived from the head alone, so "the latest head of a trail" and "the heads of size N" are prefix listings of the objects themselves. There is no index object an attacker could edit or an operator could forget to update.

<prefix><partition>/<inverted size>-<head digest>.json

The partition is the lower-case hex SHA-256 of the layout tag, the tree kind and the run or trail ID, so a listing of the bucket names no run or trail. The inverted size is MaxInt64 minus the head's size, in 19 decimal digits: stores list names in ascending order, so the largest (newest) head of a partition sorts first and "latest" is a listing of one name. The digest is the first 16 bytes (32 hex digits) of the SHA-256 of the object's body, so the same head is always the same name (a retry is idempotent) and two different heads of one size (two processes anchoring one run around a lease handoff) are two objects, neither overwriting the other.

Index

Constants

View Source
const (
	// SizeDigits is the width of the inverted size in an object name.
	SizeDigits = 19
	// DigestHex is the length of the digest in an object name, in hex digits.
	DigestHex = 32
	// ObjectSuffix ends every object name.
	ObjectSuffix = ".json"
	// PartitionLen is the length of a partition name (a hex SHA-256).
	PartitionLen = 64
)
View Source
const MaxObjectBytes = 1 << 20

MaxObjectBytes bounds what is read for one object: a head is a few hundred bytes plus the run ID, and no object an anchor writes is near this size.

Variables

This section is empty.

Functions

func CheckEquivocation

func CheckEquivocation(heads []audit.SignedTreeHead, errEquivocation error) error

CheckEquivocation returns an error wrapping errEquivocation when two heads of one tree and size commit to different roots: the log key signed two histories. Two heads of one size with the same root (two processes anchoring one run around a lease handoff) are not equivocation. heads need not be sorted. They must be the heads of one tree (one kind and run ID): heads of two trees are an error, not a verdict, since two runs may share a size.

func InvertedSize

func InvertedSize(size int) string

InvertedSize is the sortable, fixed-width form of a head size: larger sizes sort first.

func ObjectName

func ObjectName(size int, body []byte) string

ObjectName is the name of a body within its partition: inverted size, then its digest.

func SplitKey

func SplitKey(prefix, key string) (partition, name string, ok bool)

SplitKey splits a key under prefix into its partition and name.

func SuspectID

func SuspectID(id string) string

SuspectID returns a reason when a run or trail ID looks like it holds personal data, or "". The anchor is write-once: an object cannot be erased before its retention ends, and a signed head carries its run ID in clear (the ID is part of the signed bytes). The check is a tripwire for the obvious cases and not a proof that an ID is free of personal data. It reports:

  • an '@' with something before it and a dotted host (or localhost) after it, an email address, in any ID but for the exact notification trail prefix notifyOpenPrefix, which is not examined;
  • a run of suspectDigits or more digits, or of suspectSeparatedDigits or more digits that spaces and hyphens separate (an account or card number).

It does not report a name, an address, a free-text description, an email address written with spaces or an obfuscation, a number in another notation, or anything that merely looks opaque.

Types

type Layout

type Layout struct {
	// Format is the format string of the object body, for example "bide.audit.s3-anchor.v1".
	Format string
	// PartitionTag seeds the partition hash. It ends in a NUL byte, so that no tag is a prefix of
	// another, and differs from every other backend's.
	PartitionTag string
	// ErrObject is wrapped by every error Decode returns, and by the reader's other "not a valid
	// anchored object" errors.
	ErrObject error
	// ErrBadSignature is wrapped (with ErrObject) by the error Decode returns for a well-formed head
	// whose signature does not verify.
	ErrBadSignature error
}

Layout is one backend's naming and encoding of anchored heads.

func (Layout) Collides

func (l Layout) Collides(other Layout) bool

Collides reports whether two layouts could take each other's objects: the same format or the same partition tag. Backends must not collide; only a test that sees both layouts can check it, so each backend module tests that its Layout does not collide with the other backends'.

func (Layout) Decode

func (l Layout) Decode(partition, name string, body []byte, v audit.Verifier) (audit.SignedTreeHead, error)

Decode checks that body is the object the key names (partition and name) and returns its head. It checks the body strictly (no unknown fields), the format, the partition (from the head's kind and run ID), the size and the digest the name carries. When v is not nil it also verifies the head's signature. Every failure wraps l.ErrObject; a signature that does not verify also wraps l.ErrBadSignature. A Layout that fails Validate is refused with that error.

With a nil v, Decode is a structural check only: the result says the object is the canonical encoding of a head that its name describes, never that the head is authentic. Every Reader passes its Verifier.

func (Layout) Encode

func (l Layout) Encode(sth audit.SignedTreeHead) ([]byte, error)

Encode is the body of the object that anchors sth. It is deterministic: the same head is the same bytes, so the same name.

func (Layout) Key

func (l Layout) Key(prefix, kind, runID string, size int, body []byte) string

Key is the full key of a body for a head of the given kind, run and size.

func (Layout) LoadDir

func (l Layout) LoadDir(dir string, v audit.Verifier, errNotFound, errEquivocation error) ([]audit.SignedTreeHead, error)

LoadDir reads a downloaded set of anchored objects (for example the output of "aws s3 sync" or "gcloud storage cp -r" of the anchor prefix) and verifies each against v. The set is every regular file under dir (a directory) whose path ends in <partition>/<object name>; any other file, a symbolic link, a file larger than MaxObjectBytes and an object that does not decode under l are errors wrapping l.ErrObject (and l.ErrBadSignature for a signature that does not verify), so a stray or renamed file is not ignored. It needs no network. It returns the heads sorted by partition, then size.

A directory with no heads is an error wrapping errNotFound: nothing to verify must never read as verified. Two validly signed heads of one tree and size with different roots in one partition are an error wrapping errEquivocation (CheckEquivocation, per partition, since a partition is one tree). LoadDir cannot show the objects' lock state (the files carry none), and cannot show that the directory is complete: it shows only what was exported, so compare its newest head with the live store.

func (Layout) Partition

func (l Layout) Partition(kind, runID string) string

Partition is the partition name of a tree kind and run ID.

func (Layout) Validate

func (l Layout) Validate() error

Validate reports why l cannot be used: an empty format, a partition tag that does not end in a NUL byte (so that one tag could be a prefix of another), or a missing error value (an error wrapping a nil error would match nothing). A backend checks its own Layout, in a test or at start.

type Name

type Name struct {
	Size   int
	Digest string
}

Name is the parts of an object name.

func ParseName

func ParseName(name string) (Name, bool)

ParseName splits "<inverted size>-<digest>.json". ok is false for any other name.

Jump to

Keyboard shortcuts

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