tka: refer consistently to "DisablementValues"

This avoids putting "DisablementSecrets" in the JSON output from
`tailscale lock log`, which is potentially scary to somebody who doesn't
understand the distinction.

AUMs are stored and transmitted in CBOR-encoded format, which uses an
integer rather than a string key, so this doesn't break already-created
TKAs.

Fixes #19189

Change-Id: I15b4e81a7cef724a450bafcfa0b938da223c78c9
Signed-off-by: Alex Chan <alexc@tailscale.com>
This commit is contained in:
Alex Chan
2026-03-31 11:14:50 +01:00
committed by Alex Chan
parent 990d25c97d
commit 4ffb92d7f6
17 changed files with 117 additions and 116 deletions
-2
View File
@@ -28,8 +28,6 @@ func (lc *Client) NetworkLockStatus(ctx context.Context) (*ipnstate.NetworkLockS
} }
// NetworkLockInit initializes the tailnet key authority. // NetworkLockInit initializes the tailnet key authority.
//
// TODO(tom): Plumb through disablement secrets.
func (lc *Client) NetworkLockInit(ctx context.Context, keys []tka.Key, disablementValues [][]byte, supportDisablement []byte) (*ipnstate.NetworkLockStatus, error) { func (lc *Client) NetworkLockInit(ctx context.Context, keys []tka.Key, disablementValues [][]byte, supportDisablement []byte) (*ipnstate.NetworkLockStatus, error) {
var b bytes.Buffer var b bytes.Buffer
type initRequest struct { type initRequest struct {
@@ -76,8 +76,8 @@ func toLogMessageV1(aum tka.AUM, update ipnstate.NetworkLockUpdate) logMessageV1
if h := state.LastAUMHash; h != nil { if h := state.LastAUMHash; h != nil {
expandedState.LastAUMHash = h.String() expandedState.LastAUMHash = h.String()
} }
for _, secret := range state.DisablementSecrets { for _, secret := range state.DisablementValues {
expandedState.DisablementSecrets = append(expandedState.DisablementSecrets, fmt.Sprintf("%x", secret)) expandedState.DisablementValues = append(expandedState.DisablementValues, fmt.Sprintf("%x", secret))
} }
for _, key := range state.Keys { for _, key := range state.Keys {
expandedState.Keys = append(expandedState.Keys, toTKAKeyV1(&key)) expandedState.Keys = append(expandedState.Keys, toTKAKeyV1(&key))
@@ -180,9 +180,13 @@ type expandedStateV1 struct {
// LastAUMHash is the blake2s digest of the last-applied AUM. // LastAUMHash is the blake2s digest of the last-applied AUM.
LastAUMHash string `json:"LastAUMHash,omitzero"` LastAUMHash string `json:"LastAUMHash,omitzero"`
// DisablementSecrets are KDF-derived values which can be used // DisablementValues are KDF-derived values used to verify that a caller
// to turn off the TKA in the event of a consensus-breaking bug. // possesses a valid DisablementSecret. These values are used during the
DisablementSecrets []string // Tailnet Lock deactivation process.
//
// These are safe to share publicly or store in the clear. They cannot be
// used to derive the original DisablementSecret.
DisablementValues []string
// Keys are the public keys of either: // Keys are the public keys of either:
// //
+1 -1
View File
@@ -672,7 +672,7 @@ func nlDescribeUpdate(update ipnstate.NetworkLockUpdate, color bool) (string, er
case tka.AUMCheckpoint.String(): case tka.AUMCheckpoint.String():
fmt.Fprintln(&stanza, "Disablement values:") fmt.Fprintln(&stanza, "Disablement values:")
for _, v := range aum.State.DisablementSecrets { for _, v := range aum.State.DisablementValues {
fmt.Fprintf(&stanza, " - %x\n", v) fmt.Fprintf(&stanza, " - %x\n", v)
} }
fmt.Fprintln(&stanza, "Keys:") fmt.Fprintln(&stanza, "Keys:")
+2 -2
View File
@@ -54,7 +54,7 @@ func TestNetworkLockLogOutput(t *testing.T) {
Meta: map[string]string{"en": "one", "de": "eins", "es": "uno"}, Meta: map[string]string{"en": "one", "de": "eins", "es": "uno"},
}, },
}, },
DisablementSecrets: [][]byte{ DisablementValues: [][]byte{
{1, 2, 3}, {1, 2, 3},
{4, 5, 6}, {4, 5, 6},
{7, 8, 9}, {7, 8, 9},
@@ -125,7 +125,7 @@ KeyID: tlpub:0202
"MessageKind": "checkpoint", "MessageKind": "checkpoint",
"PrevAUMHash": "BKVVXHOVBW7Y7YXYTLVVLMNSYG6DS5GVRVSYZLASNU3AQKA732XQ", "PrevAUMHash": "BKVVXHOVBW7Y7YXYTLVVLMNSYG6DS5GVRVSYZLASNU3AQKA732XQ",
"State": { "State": {
"DisablementSecrets": [ "DisablementValues": [
"010203", "010203",
"040506", "040506",
"070809" "070809"
+1 -6
View File
@@ -655,12 +655,7 @@ func (b *LocalBackend) NetworkLockInit(keys []tka.Key, disablementValues [][]byt
// just in case something goes wrong. // just in case something goes wrong.
_, genesisAUM, err := tka.Create(tka.ChonkMem(), tka.State{ _, genesisAUM, err := tka.Create(tka.ChonkMem(), tka.State{
Keys: keys, Keys: keys,
// TODO(tom): s/tka.State.DisablementSecrets/tka.State.DisablementValues DisablementValues: disablementValues,
// This will center on consistent nomenclature:
// - DisablementSecret: value needed to disable.
// - DisablementValue: the KDF of the disablement secret, a public value.
DisablementSecrets: disablementValues,
StateID1: binary.LittleEndian.Uint64(entropy[:8]), StateID1: binary.LittleEndian.Uint64(entropy[:8]),
StateID2: binary.LittleEndian.Uint64(entropy[8:]), StateID2: binary.LittleEndian.Uint64(entropy[8:]),
}, nlPriv) }, nlPriv)
+10 -10
View File
@@ -105,7 +105,7 @@ func TestTKAEnablementFlow(t *testing.T) {
chonk := tka.ChonkMem() chonk := tka.ChonkMem()
a1, genesisAUM, err := tka.Create(chonk, tka.State{ a1, genesisAUM, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementSecrets: [][]byte{bytes.Repeat([]byte{0xa5}, 32)}, DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -196,7 +196,7 @@ func TestTKADisablementFlow(t *testing.T) {
} }
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -369,7 +369,7 @@ func TestTKASync(t *testing.T) {
controlStorage := tka.ChonkMem() controlStorage := tka.ChonkMem()
controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{
Keys: []tka.Key{key, someKey}, Keys: []tka.Key{key, someKey},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -479,7 +479,7 @@ func TestTKASyncTriggersCompact(t *testing.T) {
controlStorage.SetClock(clock) controlStorage.SetClock(clock)
controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{ controlAuthority, bootstrap, err := tka.Create(controlStorage, tka.State{
Keys: []tka.Key{key, someKey}, Keys: []tka.Key{key, someKey},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -609,7 +609,7 @@ func TestTKAFilterNetmap(t *testing.T) {
storage := tka.ChonkMem() storage := tka.ChonkMem()
authority, _, err := tka.Create(storage, tka.State{ authority, _, err := tka.Create(storage, tka.State{
Keys: []tka.Key{nlKey}, Keys: []tka.Key{nlKey},
DisablementSecrets: [][]byte{bytes.Repeat([]byte{0xa5}, 32)}, DisablementValues: [][]byte{bytes.Repeat([]byte{0xa5}, 32)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -772,7 +772,7 @@ func TestTKADisable(t *testing.T) {
} }
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -860,7 +860,7 @@ func TestTKASign(t *testing.T) {
} }
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -919,7 +919,7 @@ func TestTKAForceDisable(t *testing.T) {
} }
authority, genesis, err := tka.Create(chonk, tka.State{ authority, genesis, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key}, Keys: []tka.Key{key},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -1007,7 +1007,7 @@ func TestTKAAffectedSigs(t *testing.T) {
} }
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{tkaKey}, Keys: []tka.Key{tkaKey},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
@@ -1136,7 +1136,7 @@ func TestTKARecoverCompromisedKeyFlow(t *testing.T) {
} }
authority, _, err := tka.Create(chonk, tka.State{ authority, _, err := tka.Create(chonk, tka.State{
Keys: []tka.Key{key, compromisedKey, cosignKey}, Keys: []tka.Key{key, compromisedKey, cosignKey},
DisablementSecrets: [][]byte{tka.DisablementKDF(disablementSecret)}, DisablementValues: [][]byte{tka.DisablementKDF(disablementSecret)},
}, nlPriv) }, nlPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
+2 -2
View File
@@ -104,7 +104,7 @@ func TestSerialization(t *testing.T) {
}, },
bytes.Repeat([]byte{0}, 32)...), bytes.Repeat([]byte{0}, 32)...),
[]byte{ []byte{
0x02, // |- major type 0 (int), value 2 (second key, DisablementSecrets) 0x02, // |- major type 0 (int), value 2 (second key, DisablementValues)
0xf6, // |- major type 7 (val), value null (second value, nil) 0xf6, // |- major type 7 (val), value null (second value, nil)
0x03, // |- major type 0 (int), value 3 (third key, Keys) 0x03, // |- major type 0 (int), value 3 (third key, Keys)
0x81, // |- major type 4 (array), value 1 (one item in array) 0x81, // |- major type 4 (array), value 1 (one item in array)
@@ -182,7 +182,7 @@ func TestDeserializeExistingAUMs(t *testing.T) {
Want: AUM{ Want: AUM{
MessageKind: AUMCheckpoint, MessageKind: AUMCheckpoint,
State: &State{ State: &State{
DisablementSecrets: [][]byte{ DisablementValues: [][]byte{
fromBase64("jSwtotIRlTdbkNPV0bZZifOMIGvi1e1VsJPYu8D0tLo="), fromBase64("jSwtotIRlTdbkNPV0bZZifOMIGvi1e1VsJPYu8D0tLo="),
fromBase64("EIcFRg4lBkYrtz+t4LnGf/KLY7dg18pPjgY24eYlsdQ="), fromBase64("EIcFRg4lBkYrtz+t4LnGf/KLY7dg18pPjgY24eYlsdQ="),
fromBase64("5VU4oRQiMoq5qK00McfpwtmjcheVammLCRwzdp2Zje8="), fromBase64("5VU4oRQiMoq5qK00McfpwtmjcheVammLCRwzdp2Zje8="),
+7 -7
View File
@@ -31,7 +31,7 @@ func TestAuthorityBuilderAddKey(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -65,7 +65,7 @@ func TestAuthorityBuilderMaxKey(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -112,7 +112,7 @@ func TestAuthorityBuilderRemoveKey(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key, key2}, Keys: []Key{key, key2},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -158,7 +158,7 @@ func TestAuthorityBuilderSetKeyVote(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -194,7 +194,7 @@ func TestAuthorityBuilderSetKeyMeta(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -230,7 +230,7 @@ func TestAuthorityBuilderMultiple(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -278,7 +278,7 @@ func TestAuthorityBuilderCheckpointsAfterXUpdates(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
a, _, err := Create(storage, State{ a, _, err := Create(storage, State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
+1 -1
View File
@@ -17,7 +17,7 @@ func TestGenerateDeeplink(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
) )
a, _ := Open(c.Chonk()) a, _ := Open(c.Chonk())
+1 -1
View File
@@ -74,7 +74,7 @@ func TestNLPrivate(t *testing.T) {
k := Key{Kind: Key25519, Public: pub.Verifier(), Votes: 1} k := Key{Kind: Key25519, Public: pub.Verifier(), Votes: 1}
_, aum, err := Create(ChonkMem(), State{ _, aum, err := Create(ChonkMem(), State{
Keys: []Key{k}, Keys: []Key{k},
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)}, DisablementValues: [][]byte{bytes.Repeat([]byte{1}, 32)},
}, p) }, p)
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
+1 -1
View File
@@ -149,7 +149,7 @@ func testScenario(t *testing.T, sharedChain string, sharedOptions ...testchainOp
sharedOptions = append(sharedOptions, sharedOptions = append(sharedOptions,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))
+2 -2
View File
@@ -176,7 +176,7 @@ func TestSigNested_DeepNesting(t *testing.T) {
a, _ := Open(newTestchain(t, "G1\nG1.template = genesis", a, _ := Open(newTestchain(t, "G1\nG1.template = genesis",
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{k}, Keys: []Key{k},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}})).Chonk()) }})).Chonk())
if err := a.NodeKeyAuthorized(lastNodeKey.Public(), outer.Serialize()); err != nil { if err := a.NodeKeyAuthorized(lastNodeKey.Public(), outer.Serialize()); err != nil {
t.Errorf("NodeKeyAuthorized(lastNodeKey) failed: %v", err) t.Errorf("NodeKeyAuthorized(lastNodeKey) failed: %v", err)
@@ -241,7 +241,7 @@ func TestSigCredential(t *testing.T) {
a, _ := Open(newTestchain(t, "G1\nG1.template = genesis", a, _ := Open(newTestchain(t, "G1\nG1.template = genesis",
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{k}, Keys: []Key{k},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}})).Chonk()) }})).Chonk())
if err := a.NodeKeyAuthorized(node.Public(), nestedSig.Serialize()); err == nil { if err := a.NodeKeyAuthorized(node.Public(), nestedSig.Serialize()); err == nil {
t.Error("NodeKeyAuthorized(SigCredential, node) did not fail") t.Error("NodeKeyAuthorized(SigCredential, node) did not fail")
+18 -14
View File
@@ -29,9 +29,13 @@ type State struct {
// is the same as the LastAUMHash. // is the same as the LastAUMHash.
LastAUMHash *AUMHash `cbor:"1,keyasint"` LastAUMHash *AUMHash `cbor:"1,keyasint"`
// DisablementSecrets are KDF-derived values which can be used // DisablementValues are KDF-derived values used to verify that a caller
// to turn off the TKA in the event of a consensus-breaking bug. // possesses a valid DisablementSecret. These values are used during the
DisablementSecrets [][]byte `cbor:"2,keyasint"` // Tailnet Lock deactivation process.
//
// These are safe to share publicly or store in the clear. They cannot be
// used to derive the original DisablementSecret.
DisablementValues [][]byte `cbor:"2,keyasint"`
// Keys are the public keys of either: // Keys are the public keys of either:
// //
@@ -79,11 +83,11 @@ func (s State) Clone() State {
out.LastAUMHash = &dupe out.LastAUMHash = &dupe
} }
if s.DisablementSecrets != nil { if s.DisablementValues != nil {
out.DisablementSecrets = make([][]byte, len(s.DisablementSecrets)) out.DisablementValues = make([][]byte, len(s.DisablementValues))
for i := range s.DisablementSecrets { for i := range s.DisablementValues {
out.DisablementSecrets[i] = make([]byte, len(s.DisablementSecrets[i])) out.DisablementValues[i] = make([]byte, len(s.DisablementValues[i]))
copy(out.DisablementSecrets[i], s.DisablementSecrets[i]) copy(out.DisablementValues[i], s.DisablementValues[i])
} }
} }
@@ -114,7 +118,7 @@ var disablementSalt = []byte("tailscale network-lock disablement salt")
// key authority, but cannot be reversed to find the input secret. // 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 // When the output of this function is stored in tka state (i.e. in
// tka.State.DisablementSecrets) a call to Authority.ValidDisablement() // tka.State.DisablementValues) a call to Authority.ValidDisablement()
// with the input of this function as the argument will return true. // with the input of this function as the argument will return true.
func DisablementKDF(secret []byte) []byte { func DisablementKDF(secret []byte) []byte {
// time = 4 (3 recommended, booped to 4 to compensate for less memory) // time = 4 (3 recommended, booped to 4 to compensate for less memory)
@@ -127,7 +131,7 @@ func DisablementKDF(secret []byte) []byte {
// checkDisablement returns true for a valid disablement secret. // checkDisablement returns true for a valid disablement secret.
func (s State) checkDisablement(secret []byte) bool { func (s State) checkDisablement(secret []byte) bool {
derived := DisablementKDF(secret) derived := DisablementKDF(secret)
for _, candidate := range s.DisablementSecrets { for _, candidate := range s.DisablementValues {
if subtle.ConstantTimeCompare(derived, candidate) == 1 { if subtle.ConstantTimeCompare(derived, candidate) == 1 {
return true return true
} }
@@ -254,17 +258,17 @@ func (s *State) staticValidateCheckpoint() error {
if s.LastAUMHash != nil { if s.LastAUMHash != nil {
return errors.New("cannot specify a parent AUM") return errors.New("cannot specify a parent AUM")
} }
if len(s.DisablementSecrets) == 0 { if len(s.DisablementValues) == 0 {
return errors.New("at least one disablement secret required") return errors.New("at least one disablement secret required")
} }
if numDS := len(s.DisablementSecrets); numDS > maxDisablementSecrets { if numDS := len(s.DisablementValues); numDS > maxDisablementSecrets {
return fmt.Errorf("too many disablement secrets (%d, max %d)", numDS, maxDisablementSecrets) return fmt.Errorf("too many disablement secrets (%d, max %d)", numDS, maxDisablementSecrets)
} }
for i, ds := range s.DisablementSecrets { for i, ds := range s.DisablementValues {
if len(ds) != disablementLength { if len(ds) != disablementLength {
return fmt.Errorf("disablement[%d]: invalid length (got %d, want %d)", i, len(ds), disablementLength) return fmt.Errorf("disablement[%d]: invalid length (got %d, want %d)", i, len(ds), disablementLength)
} }
for j, ds2 := range s.DisablementSecrets { for j, ds2 := range s.DisablementValues {
if i == j { if i == j {
continue continue
} }
+10 -10
View File
@@ -36,26 +36,26 @@ func TestCloneState(t *testing.T) {
State State State State
}{ }{
{ {
"Empty", Name: "Empty",
State{}, State: State{},
}, },
{ {
"Key", Name: "Key",
State{ State: State{
Keys: []Key{{Kind: Key25519, Votes: 2, Public: []byte{5, 6, 7, 8}, Meta: map[string]string{"a": "b"}}}, Keys: []Key{{Kind: Key25519, Votes: 2, Public: []byte{5, 6, 7, 8}, Meta: map[string]string{"a": "b"}}},
}, },
}, },
{ {
"StateID", Name: "StateID",
State{ State: State{
StateID1: 42, StateID1: 42,
StateID2: 22, StateID2: 22,
}, },
}, },
{ {
"DisablementSecrets", Name: "DisablementValues",
State{ State: State{
DisablementSecrets: [][]byte{ DisablementValues: [][]byte{
{1, 2, 3, 4}, {1, 2, 3, 4},
{5, 6, 7, 8}, {5, 6, 7, 8},
}, },
@@ -155,7 +155,7 @@ func TestApplyUpdatesChain(t *testing.T) {
Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}, Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
}, PrevAUMHash: fromHex("f09bda3bb7cf6756ea9adc25770aede4b3ca8142949d6ef5ca0add29af912fd4")}, }, PrevAUMHash: fromHex("f09bda3bb7cf6756ea9adc25770aede4b3ca8142949d6ef5ca0add29af912fd4")},
}, },
State{DisablementSecrets: [][]byte{{1, 2, 3, 4}}}, State{DisablementValues: [][]byte{{1, 2, 3, 4}}},
State{ State{
Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}, Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
LastAUMHash: hashFromHex("57343671da5eea3cfb502954e976e8028bffd3540b50a043b2a65a8d8d8217d0"), LastAUMHash: hashFromHex("57343671da5eea3cfb502954e976e8028bffd3540b50a043b2a65a8d8d8217d0"),
+1 -1
View File
@@ -341,7 +341,7 @@ func TestSyncSimpleE2E(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))
+3 -3
View File
@@ -317,7 +317,7 @@ func TestMarkDescendantAUMs(t *testing.T) {
func TestMarkAncestorIntersectionAUMs(t *testing.T) { func TestMarkAncestorIntersectionAUMs(t *testing.T) {
fakeState := &State{ fakeState := &State{
Keys: []Key{{Kind: Key25519, Votes: 1}}, Keys: []Key{{Kind: Key25519, Votes: 1}},
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)}, DisablementValues: [][]byte{bytes.Repeat([]byte{1}, 32)},
} }
tcs := []struct { tcs := []struct {
@@ -543,7 +543,7 @@ func cloneMem(src, dst *Mem) {
func TestCompact(t *testing.T) { func TestCompact(t *testing.T) {
fakeState := &State{ fakeState := &State{
Keys: []Key{{Kind: Key25519, Votes: 1}}, Keys: []Key{{Kind: Key25519, Votes: 1}},
DisablementSecrets: [][]byte{bytes.Repeat([]byte{1}, 32)}, DisablementValues: [][]byte{bytes.Repeat([]byte{1}, 32)},
} }
// A & B are deleted because the new lastActiveAncestor advances beyond them. // A & B are deleted because the new lastActiveAncestor advances beyond them.
@@ -611,7 +611,7 @@ func TestCompactLongButYoung(t *testing.T) {
storage := ChonkMem() storage := ChonkMem()
auth, _, err := Create(storage, State{ auth, _, err := Create(storage, State{
Keys: []Key{ourKey, someOtherKey}, Keys: []Key{ourKey, someOtherKey},
DisablementSecrets: [][]byte{DisablementKDF(bytes.Repeat([]byte{0xa5}, 32))}, DisablementValues: [][]byte{DisablementKDF(bytes.Repeat([]byte{0xa5}, 32))},
}, ourPriv) }, ourPriv)
if err != nil { if err != nil {
t.Fatalf("tka.Create() failed: %v", err) t.Fatalf("tka.Create() failed: %v", err)
+10 -10
View File
@@ -306,7 +306,7 @@ func TestAuthorityValidDisablement(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
) )
@@ -322,7 +322,7 @@ func TestCreateBootstrapAuthority(t *testing.T) {
a1, genesisAUM, err := Create(ChonkMem(), State{ a1, genesisAUM, err := Create(ChonkMem(), State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, signer25519(priv)) }, signer25519(priv))
if err != nil { if err != nil {
t.Fatalf("Create() failed: %v", err) t.Fatalf("Create() failed: %v", err)
@@ -354,7 +354,7 @@ func TestBootstrapChonkMustBeEmpty(t *testing.T) {
key := Key{Kind: Key25519, Public: pub, Votes: 2} key := Key{Kind: Key25519, Public: pub, Votes: 2}
state := State{ state := State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
} }
// Bootstrap our chonk for the first time, which should succeed. // Bootstrap our chonk for the first time, which should succeed.
@@ -421,7 +421,7 @@ func TestAuthorityInformNonLinear(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))
@@ -466,7 +466,7 @@ func TestAuthorityInformLinear(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))
@@ -517,7 +517,7 @@ func TestInteropWithNLKey(t *testing.T) {
Public: pub2.KeyID(), Public: pub2.KeyID(),
}, },
}, },
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}, priv1) }, priv1)
if err != nil { if err != nil {
t.Errorf("tka.Create: %v", err) t.Errorf("tka.Create: %v", err)
@@ -547,11 +547,11 @@ func TestAuthorityCompact(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("checkpoint2", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{key}, Keys: []Key{key},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optKey("key", key, priv), optKey("key", key, priv),
optSignAllUsing("key")) optSignAllUsing("key"))
@@ -604,7 +604,7 @@ func TestFindParentForRewrite(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{k1}, Keys: []Key{k1},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}), optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}),
optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3}), optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3}),
@@ -673,7 +673,7 @@ func TestMakeRetroactiveRevocation(t *testing.T) {
`, `,
optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
Keys: []Key{k1}, Keys: []Key{k1},
DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})}, DisablementValues: [][]byte{DisablementKDF([]byte{1, 2, 3})},
}}), }}),
optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}), optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}),
optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3})) optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3}))