ipn/ipnlocal,tka: generate a nonce for each TKA

Signed-off-by: Tom DNetto <tom@tailscale.com>
This commit is contained in:
Tom DNetto 2022-11-16 15:38:25 -08:00 committed by Tom
parent 6708f9a93f
commit aeb80bf8cb
3 changed files with 41 additions and 1 deletions

View File

@ -6,7 +6,9 @@
import (
"bytes"
"crypto/rand"
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
@ -418,6 +420,11 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
return errors.New("no node-key: is tailscale logged in?")
}
var entropy [16]byte
if _, err := rand.Read(entropy[:]); err != nil {
return err
}
// Generates a genesis AUM representing trust in the provided keys.
// We use an in-memory tailchonk because we don't want to commit to
// the filesystem until we've finished the initialization sequence,
@ -429,6 +436,9 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
// - DisablementSecret: value needed to disable.
// - DisablementValue: the KDF of the disablement secret, a public value.
DisablementSecrets: disablementValues,
StateID1: binary.LittleEndian.Uint64(entropy[:8]),
StateID2: binary.LittleEndian.Uint64(entropy[8:]),
}, nlPriv)
if err != nil {
return fmt.Errorf("tka.Create: %v", err)

View File

@ -36,6 +36,13 @@ type State struct {
// Keys are the public keys currently trusted by the TKA.
Keys []Key `cbor:"3,keyasint"`
// StateID's are nonce's, generated on enablement and fixed for
// the lifetime of the Tailnet Key Authority. We generate 16-bytes
// worth of keyspace here just in case we come up with a cool future
// use for this.
StateID1 uint64 `cbor:"4,keyasint,omitempty"`
StateID2 uint64 `cbor:"5,keyasint,omitempty"`
}
// GetKey returns the trusted key with the specified KeyID.
@ -55,7 +62,10 @@ func (s State) GetKey(key tkatype.KeyID) (Key, error) {
// slice for encoding purposes, so an implementation of Clone()
// must take care to preserve this.
func (s State) Clone() State {
out := State{}
out := State{
StateID1: s.StateID1,
StateID2: s.StateID2,
}
if s.LastAUMHash != nil {
dupe := *s.LastAUMHash
@ -149,6 +159,13 @@ func (s State) applyVerifiedAUM(update AUM) (State, error) {
return out, nil
case AUMCheckpoint:
if update.State == nil {
return State{}, errors.New("missing checkpoint state")
}
id1Match, id2Match := update.State.StateID1 == s.StateID1, update.State.StateID2 == s.StateID2
if !id1Match || !id2Match {
return State{}, errors.New("checkpointed state has an incorrect stateID")
}
return update.State.cloneForUpdate(&update), nil
case AUMAddKey:

View File

@ -44,6 +44,13 @@ func TestCloneState(t *testing.T) {
Keys: []Key{{Kind: Key25519, Votes: 2, Public: []byte{5, 6, 7, 8}, Meta: map[string]string{"a": "b"}}},
},
},
{
"StateID",
State{
StateID1: 42,
StateID2: 22,
},
},
{
"DisablementSecrets",
State{
@ -223,6 +230,12 @@ func TestApplyUpdateErrors(t *testing.T) {
},
errors.New("parent AUMHash mismatch"),
},
{
"Bad StateID",
[]AUM{{MessageKind: AUMCheckpoint, State: &State{StateID1: 1}}},
State{Keys: []Key{{Kind: Key25519, Public: []byte{1}}}, StateID1: 42},
errors.New("checkpointed state has an incorrect stateID"),
},
}
for _, tc := range tcs {