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 ¶
const ( KindMachine = "machine" KindDevice = "device" KindRevoke = "revoke" )
Kind names, as the spec writes them and as fixtures carry them.
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 ¶
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 ¶
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 ¶
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 Parse ¶
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 ¶
Public is the fleet public key — what every machine and every paired device verifies certs under. Nil for the zero Key.
func (Key) Seed ¶
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.
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 ¶
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