fleet

package
v0.9.4 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package fleet is the fleet key and the certificates it signs (spec/fleet-trust.md): one Ed25519 keypair per relay, held by every machine and never by the Worker, whose signatures are what let a device paired on one machine be trusted by every other.

Three certificate kinds exist and no more: a machine cert (a machine naming itself and its Noise static key), a device cert (a pairing ceremony naming the device key it admitted), and a revocation (the operator naming a device key that is dead). All three are detached Ed25519 signatures over a canonical encoding of the named fields; the signed blob — canonical bytes then the 64-byte signature — is the one wire form, carried in the IK handshake's first message today and by the relay's fleet directory in the stage that follows. testdata/fleet/certs.json pins the bytes for every implementation.

Index

Constants

View Source
const (
	KindMachine = "machine"
	KindDevice  = "device"
	KindRevoke  = "revoke"
)

Kind names, as the spec writes them and as fixtures carry them.

View Source
const (

	// SeedEncodedLen is how long an encoded fleet seed is: 32 bytes of
	// Ed25519 seed in unpadded base64url, as the join line carries it.
	SeedEncodedLen = 43
)

Canonical encoding.

The spec allows canonical CBOR or a length-prefixed encoding, pinned by test vectors either way. This package is length-prefixed, deliberately: canonical CBOR buys nothing for three record shapes with fixed field sets, and costs either a dependency (Go's standard library has no CBOR) or a hand-rolled subset whose canonicity rules — definite lengths, sorted map keys, shortest-form integers — are exactly the ambiguity a canonical encoding exists to rule out. A fixed field order with explicit lengths has no map to order and no integer forms to choose between: two implementations either produce the same bytes or fail the fixture.

The layout, byte for byte:

"flue-fleet-cert/"          16 ASCII bytes — domain separation, the same
                            style as the machine-id MAC's prefix, so a
                            fleet signature can never be confused with
                            anything else the fleet key might one day sign
u8  v                       1
u8  kind                    1 machine, 2 device, 3 revoke
then the spec's fields for that kind, in the spec's order:
  machine:  str(id)  str(name)  key32(noise)   u64(iat)
  device:   key32(device)  str(name)  str(pairedOn)  u64(iat)
  revoke:   key32(device)  u64(iat)

str    = u16 big-endian byte length, then that many bytes of UTF-8
key32  = exactly 32 raw bytes, no length prefix
u64    = 8 bytes big-endian (iat is unix seconds and never negative)

The signature is Ed25519 over exactly the canonical bytes, and the signed blob is canonical || signature. The canonical part is self-delimiting — fixed layout per kind, explicit string lengths — so the blob parses unambiguously: whatever remains after the fields must be exactly the 64 signature bytes, and a blob with anything more or less is refused.

Variables

View Source
var (
	// ErrBadCert is any blob that does not parse as a signed certificate:
	// wrong prefix, wrong version, truncated fields, trailing bytes.
	ErrBadCert = errors.New("fleet: not a certificate")

	// ErrBadSignature is a well-formed blob whose signature does not verify
	// under the given fleet public key.
	ErrBadSignature = errors.New("fleet: the signature does not verify")

	// ErrNoKey is a signing or seed operation asked of a zero Key.
	ErrNoKey = errors.New("fleet: no fleet key")
)

Functions

func Encode

func Encode(c Cert) ([]byte, error)

Encode is the canonical encoding of c — the exact bytes a signature covers. Exported for the fixture generator; everything else wants Sign and Verify, which carry the signature with the bytes.

Types

type Cert

type Cert interface {
	// Kind is the spec's name for this certificate's kind.
	Kind() string
	// contains filtered or unexported methods
}

Cert is one of the three certificate kinds. It is a sealed interface — the encoding has exactly three layouts, and a fourth implementer would be bytes no verifier recognises.

func Verify

func Verify(pub ed25519.PublicKey, blob []byte) (Cert, error)

Verify parses a signed blob, checks its signature under the fleet public key, and returns the typed certificate. Every fault is an error: a blob that is not a cert, trailing bytes, a signature that does not verify. Callers must treat the returned cert as meaningful only for the checks they then perform themselves — above all that a revocation for the same key outranks a device cert regardless of either iat.

type DeviceCert

type DeviceCert struct {
	Device   []byte // the device's static X25519 public key, 32 bytes
	Name     string // the device's display name, as the ceremony recorded it
	PairedOn string // the machine id the ceremony ran on
	IAT      int64
}

DeviceCert is a pairing ceremony's outcome: minted by whichever machine ran the ceremony, at the moment it completed, naming the device key it admitted. A daemon that has never seen the key admits it on this cert alone — which is the whole point of the fleet key.

func VerifyDevice

func VerifyDevice(pub ed25519.PublicKey, blob []byte) (DeviceCert, error)

VerifyDevice is Verify for the one place that only a device cert will do: the daemon's handshake payload. A machine cert or a revocation in that position is well-signed and still refused.

func (DeviceCert) Kind

func (DeviceCert) Kind() string

type Key

type Key struct {
	// contains filtered or unexported fields
}

Key is the fleet key: the Ed25519 keypair `flue relay setup` mints, whose seed rides the join line and lands in relay.json on every machine. The zero value is "no fleet key" — Valid reports it, Sign refuses it — so a daemon on a relay from before the key existed carries exactly that.

func Mint

func Mint(r io.Reader) (Key, error)

Mint makes a fresh fleet key from r, which is crypto/rand.Reader everywhere but tests.

func Parse

func Parse(seed string) (Key, error)

Parse reads a seed as the join line spells it: 32 bytes in unpadded base64url. It is strict — padded, standard-alphabet or wrong-length input is refused rather than guessed at, because the value arrives pasted across machines and a mangled seed that half-parsed would join a machine to a fleet whose signatures it can never verify.

func (Key) Public

func (k Key) Public() ed25519.PublicKey

Public is the fleet public key — what every machine and every paired device verifies certs under. Nil for the zero Key.

func (Key) Seed

func (k Key) Seed() string

Seed is the key as the join line and relay.json carry it: the 32-byte Ed25519 seed, unpadded base64url. Empty for the zero Key.

func (Key) Sign

func (k Key) Sign(c Cert) ([]byte, error)

Sign encodes c canonically and returns the signed blob: the canonical bytes followed by the 64-byte Ed25519 signature over them.

func (Key) Valid

func (k Key) Valid() bool

Valid reports whether this is a real key rather than the zero value.

type MachineCert

type MachineCert struct {
	ID    string // the machine id, as minted by config.MintMachineID
	Name  string // the machine's display name
	Noise []byte // the daemon's static X25519 public key, 32 bytes
	IAT   int64  // unix seconds, for display; certs deliberately never expire
}

MachineCert is a machine naming itself: minted by the machine, for itself, at join or setup time. Browsers verify it to learn which Noise static key to pin for the machine; nothing daemon-side consumes it yet (the fleet directory that distributes it is the stage after this one).

func (MachineCert) Kind

func (MachineCert) Kind() string

type Revocation

type Revocation struct {
	Device []byte // the revoked device key, 32 bytes
	IAT    int64
}

Revocation is the operator killing a device key, minted by whichever machine the revoke ran on. It permanently outranks any device cert for the same key, whatever either one's timestamp says: iat is display, not precedence, and un-revoking is pairing again under a fresh key. Verifiers must implement it that way — check the revocation set before honouring a device cert, and never compare the two by time.

func (Revocation) Kind

func (Revocation) Kind() string

Jump to

Keyboard shortcuts

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