tka

package
v0.0.0-...-ded95ce Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2022 License: BSD-3-Clause Imports: 17 Imported by: 0

Documentation

Overview

Package tka (WIP) implements the Tailnet Key Authority.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoIntersection = errors.New("no intersection")

ErrNoIntersection is returned when a shared AUM could not be determined when evaluating a remote sync offer.

View Source
var ErrNoSuchKey = errors.New("key not found")

ErrNoSuchKey is returned if the key referenced by a KeyID does not exist.

Functions

func Create

func Create(storage Chonk, state State, signer Signer) (*Authority, AUM, error)

Create initializes a brand-new TKA, generating a genesis update and committing it to the given storage.

The given signer must also be present in state as a trusted key.

Do not use this to initialize a TKA that already exists, use Open() or Bootstrap() instead.

func DisablementKDF

func DisablementKDF(secret []byte) []byte

DisablementKDF computes a public value which can be stored in a key authority, but cannot be reversed to find the input secret.

When the output of this function is stored in tka state (i.e. in tka.State.DisablementSecrets) a call to Authority.ValidDisablement() with the input of this function as the argument will return true.

Types

type AUM

type AUM struct {
	MessageKind AUMKind `cbor:"1,keyasint"`
	PrevAUMHash []byte  `cbor:"2,keyasint"`

	// Key encodes a public key to be added to the key authority.
	// This field is used for AddKey AUMs.
	Key *Key `cbor:"3,keyasint,omitempty"`

	// KeyID references a public key which is part of the key authority.
	// This field is used for RemoveKey and UpdateKey AUMs.
	KeyID tkatype.KeyID `cbor:"4,keyasint,omitempty"`

	// State describes the full state of the key authority.
	// This field is used for Checkpoint AUMs.
	State *State `cbor:"5,keyasint,omitempty"`

	// Votes and Meta describe properties of a key in the key authority.
	// These fields are used for UpdateKey AUMs.
	Votes *uint             `cbor:"6,keyasint,omitempty"`
	Meta  map[string]string `cbor:"7,keyasint,omitempty"`

	// Signatures lists the signatures over this AUM.
	// CBOR key 23 is the last key which can be encoded as a single byte.
	Signatures []tkatype.Signature `cbor:"23,keyasint,omitempty"`
}

AUM describes an Authority Update Message.

The rules for adding new types of AUMs (MessageKind):

  • CBOR key IDs must never be changed.
  • New AUM types must not change semantics that are manipulated by other AUM types.
  • The serialization of existing data cannot change (in other words, if an existing serialization test in aum_test.go fails, you need to try a different approach).

The rules for adding new fields are as follows:

  • Must all be optional.
  • An unset value must not result in serialization overhead. This is necessary so the serialization of older AUMs stays the same.
  • New processing semantics of the new fields must be compatible with the behavior of old clients (which will ignore the field).
  • No floats!

func (*AUM) Hash

func (a *AUM) Hash() AUMHash

Hash returns a cryptographic digest of all AUM contents.

func (*AUM) Parent

func (a *AUM) Parent() (h AUMHash, ok bool)

Parent returns the parent's AUM hash and true, or a zero value and false if there was no parent.

func (*AUM) Serialize

func (a *AUM) Serialize() tkatype.MarshaledAUM

Serialize returns the given AUM in a serialized format.

We would implement encoding.BinaryMarshaler, except that would unfortunately get called by the cbor marshaller resulting in infinite recursion.

func (AUM) SigHash

func (a AUM) SigHash() tkatype.AUMSigHash

SigHash returns the cryptographic digest which a signature is over.

This is identical to Hash() except the Signatures are not serialized. Without this, the hash used for signatures would be circularly dependent on the signatures.

func (*AUM) StaticValidate

func (a *AUM) StaticValidate() error

StaticValidate returns a nil error if the AUM is well-formed.

func (*AUM) Unserialize

func (a *AUM) Unserialize(data []byte) error

Unserialize decodes bytes representing a marshaled AUM.

We would implement encoding.BinaryUnmarshaler, except that would unfortunately get called by the cbor unmarshaller resulting in infinite recursion.

func (*AUM) Weight

func (a *AUM) Weight(state State) uint

Weight computes the 'signature weight' of the AUM based on keys in the state machine. The caller must ensure that all signatures are valid.

More formally: W = Sum(key.votes)

AUMs with a higher weight than their siblings are preferred when resolving forks in the AUM chain.

type AUMHash

type AUMHash [blake2s.Size]byte

AUMHash represents the BLAKE2s digest of an Authority Update Message (AUM).

func (AUMHash) IsZero

func (h AUMHash) IsZero() bool

IsZero returns true if the hash is the empty value.

func (AUMHash) MarshalText

func (h AUMHash) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (AUMHash) String

func (h AUMHash) String() string

String returns the AUMHash encoded as base32. This is suitable for use as a filename, and for storing in text-preferred media.

func (*AUMHash) UnmarshalText

func (h *AUMHash) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

type AUMKind

type AUMKind uint8

AUMKind describes valid AUM types.

const (
	AUMInvalid AUMKind = iota
	// An AddKey AUM describes a new key trusted by the TKA.
	//
	// Only the Key optional field may be set.
	AUMAddKey
	// A RemoveKey AUM describes the removal of a key trusted by TKA.
	//
	// Only the KeyID optional field may be set.
	AUMRemoveKey
	// A NoOp AUM carries no information and is used in tests.
	AUMNoOp
	// A UpdateKey AUM updates the metadata or votes of an existing key.
	//
	// Only KeyID, along with either/or Meta or Votes optional fields
	// may be set.
	AUMUpdateKey
	// A Checkpoint AUM specifies the full state of the TKA.
	//
	// Only the State optional field may be set.
	AUMCheckpoint
)

Valid AUM types. Do NOT reorder.

func (AUMKind) String

func (k AUMKind) String() string

type Authority

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

Authority is a Tailnet Key Authority. This type is the main coupling point to the rest of the tailscale client.

Authority objects can either be created from an existing, non-empty tailchonk (via tka.Open()), or created from scratch using tka.Bootstrap() or tka.Create().

func Bootstrap

func Bootstrap(storage Chonk, bootstrap AUM) (*Authority, error)

Bootstrap initializes a TKA based on the given checkpoint.

Call this when setting up a new nodes' TKA, but other nodes with initialized TKA's exist.

Pass the returned genesis AUM from Create(), or a later checkpoint AUM.

TODO(tom): We should test an authority bootstrapped from a later checkpoint works fine with sync and everything.

func Open

func Open(storage Chonk) (*Authority, error)

Open initializes an existing TKA from the given tailchonk.

Only use this if the current node has initialized an Authority before. If a TKA exists on other nodes but theres nothing locally, use Bootstrap(). If no TKA exists anywhere and you are creating it for the first time, use New().

func (*Authority) Clone

func (a *Authority) Clone() *Authority

Clone duplicates the Authority structure.

func (*Authority) Head

func (a *Authority) Head() AUMHash

Head returns the AUM digest of the latest update applied to the state machine.

func (*Authority) Inform

func (a *Authority) Inform(storage Chonk, updates []AUM) error

Inform is the same as InformIdempotent, except the state of the Authority is updated in-place.

func (*Authority) InformIdempotent

func (a *Authority) InformIdempotent(storage Chonk, updates []AUM) (Authority, error)

InformIdempotent returns a new Authority based on applying the given updates, with the given updates committed to storage.

If any of the updates could not be applied:

  • An error is returned
  • No changes to storage are made.

MissingAUMs() should be used to get a list of updates appropriate for this function. In any case, updates should be ordered oldest to newest.

func (*Authority) KeyTrusted

func (a *Authority) KeyTrusted(keyID tkatype.KeyID) bool

KeyTrusted returns true if the given keyID is trusted by the tailnet key authority.

func (*Authority) MissingAUMs

func (a *Authority) MissingAUMs(storage Chonk, remoteOffer SyncOffer) ([]AUM, error)

MissingAUMs returns AUMs a remote may be missing based on the remotes' SyncOffer.

func (*Authority) NewUpdater

func (a *Authority) NewUpdater(signer Signer) *UpdateBuilder

NewUpdater returns a builder you can use to make changes to the tailnet key authority.

The provided signer function, if non-nil, is called with each update to compute and apply signatures.

Updates are specified by calling methods on the returned UpdatedBuilder. Call Finalize() when you are done to obtain the specific update messages which actuate the changes.

func (*Authority) NodeKeyAuthorized

func (a *Authority) NodeKeyAuthorized(nodeKey key.NodePublic, nodeKeySignature tkatype.MarshaledSignature) error

NodeKeyAuthorized checks if the provided nodeKeySignature authorizes the given node key.

func (*Authority) SyncOffer

func (a *Authority) SyncOffer(storage Chonk) (SyncOffer, error)

SyncOffer returns an abbreviated description of the current AUM chain, which can be used to synchronize with another (untrusted) Authority instance.

The returned SyncOffer structure should be transmitted to the remote Authority, which should call MissingAUMs() using it to determine AUMs which need to be transmitted. This list of AUMs from the remote can then be applied locally with Inform().

This SyncOffer + AUM exchange should be performed by both ends, because its possible that either end has AUMs that the other needs to find out about.

func (*Authority) ValidDisablement

func (a *Authority) ValidDisablement(secret []byte) bool

ValidDisablement returns true if the disablement secret was correct.

If this method returns true, the caller should shut down the authority and purge all network-lock state.

type Chonk

type Chonk interface {
	// AUM returns the AUM with the specified digest.
	//
	// If the AUM does not exist, then os.ErrNotExist is returned.
	AUM(hash AUMHash) (AUM, error)

	// ChildAUMs returns all AUMs with a specified previous
	// AUM hash.
	ChildAUMs(prevAUMHash AUMHash) ([]AUM, error)

	// CommitVerifiedAUMs durably stores the provided AUMs.
	// Callers MUST ONLY provide AUMs which are verified (specifically,
	// a call to aumVerify() must return a nil error).
	// as the implementation assumes that only verified AUMs are stored.
	CommitVerifiedAUMs(updates []AUM) error

	// Heads returns AUMs for which there are no children. In other
	// words, the latest AUM in all possible chains (the 'leaves').
	Heads() ([]AUM, error)

	// SetLastActiveAncestor is called to record the oldest-known AUM
	// that contributed to the current state. This value is used as
	// a hint on next startup to determine which chain to pick when computing
	// the current state, if there are multiple distinct chains.
	SetLastActiveAncestor(hash AUMHash) error

	// LastActiveAncestor returns the oldest-known AUM that was (in a
	// previous run) an ancestor of the current state. This is used
	// as a hint to pick the correct chain in the event that the Chonk stores
	// multiple distinct chains.
	LastActiveAncestor() (*AUMHash, error)
}

Chonk implementations provide durable storage for AUMs and other TKA state.

All methods must be thread-safe.

The name 'tailchonk' was coined by @catzkorn.

type FS

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

FS implements filesystem storage of TKA state.

FS implements the Chonk interface.

func ChonkDir

func ChonkDir(dir string) (*FS, error)

ChonkDir returns an implementation of Chonk which uses the given directory to store TKA state.

func (*FS) AUM

func (c *FS) AUM(hash AUMHash) (AUM, error)

AUM returns the AUM with the specified digest.

If the AUM does not exist, then os.ErrNotExist is returned.

func (*FS) ChildAUMs

func (c *FS) ChildAUMs(prevAUMHash AUMHash) ([]AUM, error)

AUM returns any known AUMs with a specific parent hash.

func (*FS) CommitVerifiedAUMs

func (c *FS) CommitVerifiedAUMs(updates []AUM) error

CommitVerifiedAUMs durably stores the provided AUMs. Callers MUST ONLY provide AUMs which are verified (specifically, a call to aumVerify must return a nil error), as the implementation assumes that only verified AUMs are stored.

func (*FS) Heads

func (c *FS) Heads() ([]AUM, error)

Heads returns AUMs for which there are no children. In other words, the latest AUM in all possible chains (the 'leaves').

Heads is expected to be called infrequently compared to AUM() or ChildAUMs(), so we haven't put any work into maintaining an index. Instead, the full set of AUMs is scanned.

func (*FS) LastActiveAncestor

func (c *FS) LastActiveAncestor() (*AUMHash, error)

LastActiveAncestor returns the oldest-known AUM that was (in a previous run) an ancestor of the current state. This is used as a hint to pick the correct chain in the event that the Chonk stores multiple distinct chains.

Nil is returned if no last-active ancestor is set.

func (*FS) SetLastActiveAncestor

func (c *FS) SetLastActiveAncestor(hash AUMHash) error

SetLastActiveAncestor is called to record the oldest-known AUM that contributed to the current state. This value is used as a hint on next startup to determine which chain to pick when computing the current state, if there are multiple distinct chains.

type Key

type Key struct {
	Kind KeyKind `cbor:"1,keyasint"`

	// Votes describes the weight applied to signatures using this key.
	// Weighting is used to deterministically resolve branches in the AUM
	// chain (i.e. forks, where two AUMs exist with the same parent).
	Votes uint `cbor:"2,keyasint"`

	// Public encodes the public key of the key. For 25519 keys,
	// this is simply the point on the curve representing the public
	// key.
	Public []byte `cbor:"3,keyasint"`

	// Meta describes arbitrary metadata about the key. This could be
	// used to store the name of the key, for instance.
	Meta map[string]string `cbor:"12,keyasint,omitempty"`
}

Key describes the public components of a key known to network-lock.

func (Key) Clone

func (k Key) Clone() Key

Clone makes an independent copy of Key.

NOTE: There is a difference between a nil slice and an empty slice for encoding purposes, so an implementation of Clone() must take care to preserve this.

func (Key) ID

func (k Key) ID() tkatype.KeyID

func (Key) StaticValidate

func (k Key) StaticValidate() error

type KeyKind

type KeyKind uint8

KeyKind describes the different varieties of a Key.

const (
	KeyInvalid KeyKind = iota
	Key25519
)

Valid KeyKind values.

func (KeyKind) String

func (k KeyKind) String() string

type Mem

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

Mem implements in-memory storage of TKA state, suitable for tests.

Mem implements the Chonk interface.

func (*Mem) AUM

func (c *Mem) AUM(hash AUMHash) (AUM, error)

AUM returns the AUM with the specified digest.

func (*Mem) ChildAUMs

func (c *Mem) ChildAUMs(prevAUMHash AUMHash) ([]AUM, error)

ChildAUMs returns all AUMs with a specified previous AUM hash.

func (*Mem) CommitVerifiedAUMs

func (c *Mem) CommitVerifiedAUMs(updates []AUM) error

CommitVerifiedAUMs durably stores the provided AUMs. Callers MUST ONLY provide well-formed and verified AUMs, as the rest of the TKA implementation assumes that only verified AUMs are stored.

func (*Mem) Heads

func (c *Mem) Heads() ([]AUM, error)

Heads returns AUMs for which there are no children. In other words, the latest AUM in all chains (the 'leaf').

func (*Mem) LastActiveAncestor

func (c *Mem) LastActiveAncestor() (*AUMHash, error)

func (*Mem) Orphans

func (c *Mem) Orphans() ([]AUM, error)

Orphans returns all AUMs which do not have a parent.

func (*Mem) SetLastActiveAncestor

func (c *Mem) SetLastActiveAncestor(hash AUMHash) error

type NodeKeySignature

type NodeKeySignature struct {
	// SigKind identifies the variety of signature.
	SigKind SigKind `cbor:"1,keyasint"`
	// Pubkey identifies the key.NodePublic which is being authorized.
	// SigCredential signatures do not use this field.
	Pubkey []byte `cbor:"2,keyasint,omitempty"`

	// KeyID identifies which key in the tailnet key authority should
	// be used to verify this signature. Only set for SigDirect and
	// SigCredential signature kinds.
	KeyID []byte `cbor:"3,keyasint,omitempty"`

	// Signature is the packed (R, S) ed25519 signature over all other
	// fields of the structure.
	Signature []byte `cbor:"4,keyasint,omitempty"`

	// Nested describes a NodeKeySignature which authorizes the node-key
	// used as Pubkey. Only used for SigRotation signatures.
	Nested *NodeKeySignature `cbor:"5,keyasint,omitempty"`

	// WrappingPubkey specifies the ed25519 public key which must be used
	// to sign a Signature which embeds this one.
	//
	// For SigRotation signatures multiple levels deep, intermediate
	// signatures may omit this value, in which case the parent WrappingPubkey
	// is used.
	//
	// SigCredential signatures use this field to specify the public key
	// they are certifying, following the usual semanticsfor WrappingPubkey.
	WrappingPubkey []byte `cbor:"6,keyasint,omitempty"`
}

NodeKeySignature encapsulates a signature that authorizes a specific node key, based on verification from keys in the tailnet key authority.

func (*NodeKeySignature) Serialize

Serialize returns the given NKS in a serialized format.

We would implement encoding.BinaryMarshaler, except that would unfortunately get called by the cbor marshaller resulting in infinite recursion.

func (NodeKeySignature) SigHash

func (s NodeKeySignature) SigHash() [blake2s.Size]byte

SigHash returns the cryptographic digest which a signature is over.

This is a hash of the serialized structure, sans the signature. Without this exclusion, the hash used for the signature would be circularly dependent on the signature.

func (*NodeKeySignature) Unserialize

func (s *NodeKeySignature) Unserialize(data []byte) error

Unserialize decodes bytes representing a marshaled NKS.

We would implement encoding.BinaryUnmarshaler, except that would unfortunately get called by the cbor unmarshaller resulting in infinite recursion.

type SigKind

type SigKind uint8

SigKind describes valid NodeKeySignature types.

const (
	SigInvalid SigKind = iota
	// SigDirect describes a signature over a specific node key, signed
	// by a key in the tailnet key authority referenced by the specified keyID.
	SigDirect
	// SigRotation describes a signature over a specific node key, signed
	// by the rotation key authorized by a nested NodeKeySignature structure.
	//
	// While it is possible to nest rotations multiple times up to the CBOR
	// nesting limit, it is intended that nodes simply regenerate their outer
	// SigRotation signature and sign it again with their rotation key. That
	// way, SigRotation nesting should only be 2 deep in the common case.
	SigRotation
	// SigCredential describes a signature over a specific public key, signed
	// by a key in the tailnet key authority referenced by the specified keyID.
	// In effect, SigCredential delegates the ability to make a signature to
	// a different public/private key pair.
	//
	// It is intended that a different public/private key pair be generated
	// for each different SigCredential that is created. Implementors must
	// take care that the private side is only known to the entity that needs
	// to generate the wrapping SigRotation signature, and it is immediately
	// discarded after use.
	//
	// SigCredential is expected to be nested in a SigRotation signature.
	SigCredential
)

func (SigKind) String

func (s SigKind) String() string

type Signer

type Signer interface {
	// SignAUM returns signatures for the AUM encoded by the given AUMSigHash.
	SignAUM(tkatype.AUMSigHash) ([]tkatype.Signature, error)
}

Types implementing Signer can sign update messages.

type State

type State struct {
	// LastAUMHash is the blake2s digest of the last-applied AUM.
	// Because AUMs are strictly ordered and form a hash chain, we
	// check the previous AUM hash in an update we are applying
	// is the same as the LastAUMHash.
	LastAUMHash *AUMHash `cbor:"1,keyasint"`

	// DisablementSecrets are KDF-derived values which can be used
	// to turn off the TKA in the event of a consensus-breaking bug.
	//
	// TODO(tom): This is an alpha feature, remove this mechanism once
	//            we have confidence in our implementation.
	DisablementSecrets [][]byte `cbor:"2,keyasint"`

	// Keys are the public keys currently trusted by the TKA.
	Keys []Key `cbor:"3,keyasint"`
}

State describes Tailnet Key Authority state at an instant in time.

State is mutated by applying Authority Update Messages (AUMs), resulting in a new State.

func (State) Clone

func (s State) Clone() State

Clone makes an independent copy of State.

NOTE: There is a difference between a nil slice and an empty slice for encoding purposes, so an implementation of Clone() must take care to preserve this.

func (State) GetKey

func (s State) GetKey(key tkatype.KeyID) (Key, error)

GetKey returns the trusted key with the specified KeyID.

type SyncOffer

type SyncOffer struct {
	Head      AUMHash
	Ancestors []AUMHash
}

SyncOffer conveys information about the current head & ancestor AUMs, for the purpose of synchronization with some remote end.

Ancestors should contain a subset of the ancestors of the chain. The last entry in that slice is the oldest-known AUM in the chain.

type UpdateBuilder

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

UpdateBuilder implements a builder for changes to the tailnet key authority.

Finalize must be called to compute the update messages, which must then be applied to all Authority objects using Inform().

func (*UpdateBuilder) AddKey

func (b *UpdateBuilder) AddKey(key Key) error

AddKey adds a new key to the authority.

func (*UpdateBuilder) Finalize

func (b *UpdateBuilder) Finalize(storage Chonk) ([]AUM, error)

Finalize returns the set of update message to actuate the update.

func (*UpdateBuilder) RemoveKey

func (b *UpdateBuilder) RemoveKey(keyID tkatype.KeyID) error

RemoveKey removes a key from the authority.

func (*UpdateBuilder) SetKeyMeta

func (b *UpdateBuilder) SetKeyMeta(keyID tkatype.KeyID, meta map[string]string) error

SetKeyMeta updates key-value metadata stored against an existing key.

TODO(tom): Provide an API to update specific values rather than the whole map.

func (*UpdateBuilder) SetKeyVote

func (b *UpdateBuilder) SetKeyVote(keyID tkatype.KeyID, votes uint) error

SetKeyVote updates the number of votes of an existing key.

Jump to

Keyboard shortcuts

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