tailscale/tka/aum_test.go

254 lines
8.4 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package tka
import (
"bytes"
"testing"
"github.com/google/go-cmp/cmp"
"golang.org/x/crypto/blake2s"
"tailscale.com/types/tkatype"
)
func TestSerialization(t *testing.T) {
uint2 := uint(2)
var fakeAUMHash AUMHash
tcs := []struct {
Name string
AUM AUM
Expect []byte
}{
{
"AddKey",
AUM{MessageKind: AUMAddKey, Key: &Key{}},
[]byte{
0xa3, // major type 5 (map), 3 items
0x01, // |- major type 0 (int), value 1 (first key, MessageKind)
0x01, // |- major type 0 (int), value 1 (first value, AUMAddKey)
0x02, // |- major type 0 (int), value 2 (second key, PrevAUMHash)
0xf6, // |- major type 7 (val), value null (second value, nil)
0x03, // |- major type 0 (int), value 3 (third key, Key)
0xa3, // |- major type 5 (map), 3 items (type Key)
0x01, // |- major type 0 (int), value 1 (first key, Kind)
0x00, // |- major type 0 (int), value 0 (first value)
0x02, // |- major type 0 (int), value 2 (second key, Votes)
0x00, // |- major type 0 (int), value 0 (first value)
0x03, // |- major type 0 (int), value 3 (third key, Public)
0xf6, // |- major type 7 (val), value null (third value, nil)
},
},
{
"RemoveKey",
AUM{MessageKind: AUMRemoveKey, KeyID: []byte{1, 2}},
[]byte{
0xa3, // major type 5 (map), 3 items
0x01, // |- major type 0 (int), value 1 (first key, MessageKind)
0x02, // |- major type 0 (int), value 2 (first value, AUMRemoveKey)
0x02, // |- major type 0 (int), value 2 (second key, PrevAUMHash)
0xf6, // |- major type 7 (val), value null (second value, nil)
0x04, // |- major type 0 (int), value 4 (third key, KeyID)
0x42, // |- major type 2 (byte string), 2 items
0x01, // |- major type 0 (int), value 1 (byte 1)
0x02, // |- major type 0 (int), value 2 (byte 2)
},
},
{
"UpdateKey",
AUM{MessageKind: AUMUpdateKey, Votes: &uint2, KeyID: []byte{1, 2}, Meta: map[string]string{"a": "b"}},
[]byte{
0xa5, // major type 5 (map), 5 items
0x01, // |- major type 0 (int), value 1 (first key, MessageKind)
0x04, // |- major type 0 (int), value 4 (first value, AUMUpdateKey)
0x02, // |- major type 0 (int), value 2 (second key, PrevAUMHash)
0xf6, // |- major type 7 (val), value null (second value, nil)
0x04, // |- major type 0 (int), value 4 (third key, KeyID)
0x42, // |- major type 2 (byte string), 2 items
0x01, // |- major type 0 (int), value 1 (byte 1)
0x02, // |- major type 0 (int), value 2 (byte 2)
0x06, // |- major type 0 (int), value 6 (fourth key, Votes)
0x02, // |- major type 0 (int), value 2 (forth value, 2)
0x07, // |- major type 0 (int), value 7 (fifth key, Meta)
0xa1, // |- major type 5 (map), 1 item (map[string]string type)
0x61, // |- major type 3 (text string), value 1 (first key, one byte long)
0x61, // |- byte 'a'
0x61, // |- major type 3 (text string), value 1 (first value, one byte long)
0x62, // |- byte 'b'
},
},
{
"Checkpoint",
AUM{MessageKind: AUMCheckpoint, PrevAUMHash: []byte{1, 2}, State: &State{
LastAUMHash: &fakeAUMHash,
Keys: []Key{
{Kind: Key25519, Public: []byte{5, 6}},
},
}},
append(
append([]byte{
0xa3, // major type 5 (map), 3 items
0x01, // |- major type 0 (int), value 1 (first key, MessageKind)
0x05, // |- major type 0 (int), value 5 (first value, AUMCheckpoint)
0x02, // |- major type 0 (int), value 2 (second key, PrevAUMHash)
0x42, // |- major type 2 (byte string), 2 items (second value)
0x01, // |- major type 0 (int), value 1 (byte 1)
0x02, // |- major type 0 (int), value 2 (byte 2)
0x05, // |- major type 0 (int), value 5 (third key, State)
0xa3, // |- major type 5 (map), 3 items (third value, State type)
0x01, // |- major type 0 (int), value 1 (first key, LastAUMHash)
0x58, 0x20, // |- major type 2 (byte string), 32 items (first value)
},
bytes.Repeat([]byte{0}, 32)...),
[]byte{
0x02, // |- major type 0 (int), value 2 (second key, DisablementSecrets)
0xf6, // |- major type 7 (val), value null (second value, nil)
0x03, // |- major type 0 (int), value 3 (third key, Keys)
0x81, // |- major type 4 (array), value 1 (one item in array)
0xa3, // |- major type 5 (map), 3 items (Key type)
0x01, // |- major type 0 (int), value 1 (first key, Kind)
0x01, // |- major type 0 (int), value 1 (first value, Key25519)
0x02, // |- major type 0 (int), value 2 (second key, Votes)
0x00, // |- major type 0 (int), value 0 (second value, 0)
0x03, // |- major type 0 (int), value 3 (third key, Public)
0x42, // |- major type 2 (byte string), 2 items (third value)
0x05, // |- major type 0 (int), value 5 (byte 5)
0x06, // |- major type 0 (int), value 6 (byte 6)
}...),
},
{
"Signature",
AUM{MessageKind: AUMAddKey, Signatures: []tkatype.Signature{{KeyID: []byte{1}}}},
[]byte{
0xa3, // major type 5 (map), 3 items
0x01, // |- major type 0 (int), value 1 (first key, MessageKind)
0x01, // |- major type 0 (int), value 1 (first value, AUMAddKey)
0x02, // |- major type 0 (int), value 2 (second key, PrevAUMHash)
0xf6, // |- major type 7 (val), value null (second value, nil)
0x17, // |- major type 0 (int), value 22 (third key, Signatures)
0x81, // |- major type 4 (array), value 1 (one item in array)
0xa2, // |- major type 5 (map), 2 items (Signature type)
0x01, // |- major type 0 (int), value 1 (first key, KeyID)
0x41, // |- major type 2 (byte string), 1 item
0x01, // |- major type 0 (int), value 1 (byte 1)
0x02, // |- major type 0 (int), value 2 (second key, Signature)
0xf6, // |- major type 7 (val), value null (second value, nil)
},
},
}
for _, tc := range tcs {
t.Run(tc.Name, func(t *testing.T) {
data := []byte(tc.AUM.Serialize())
if diff := cmp.Diff(tc.Expect, data); diff != "" {
t.Errorf("serialization differs (-want, +got):\n%s", diff)
}
var decodedAUM AUM
if err := decodedAUM.Unserialize(data); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if diff := cmp.Diff(tc.AUM, decodedAUM); diff != "" {
t.Errorf("unmarshalled version differs (-want, +got):\n%s", diff)
}
})
}
}
func TestAUMWeight(t *testing.T) {
var fakeKeyID [blake2s.Size]byte
testingRand(t, 1).Read(fakeKeyID[:])
pub, _ := testingKey25519(t, 1)
key := Key{Kind: Key25519, Public: pub, Votes: 2}
pub, _ = testingKey25519(t, 2)
key2 := Key{Kind: Key25519, Public: pub, Votes: 2}
tcs := []struct {
Name string
AUM AUM
State State
Want uint
}{
{
"Empty",
AUM{},
State{},
0,
},
{
"Key unknown",
AUM{
Signatures: []tkatype.Signature{{KeyID: fakeKeyID[:]}},
},
State{},
0,
},
{
"Unary key",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.MustID()}},
},
State{
Keys: []Key{key},
},
2,
},
{
"Multiple keys",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key2.MustID()}},
},
State{
Keys: []Key{key, key2},
},
4,
},
{
"Double use",
AUM{
Signatures: []tkatype.Signature{{KeyID: key.MustID()}, {KeyID: key.MustID()}},
},
State{
Keys: []Key{key},
},
2,
},
}
for _, tc := range tcs {
t.Run(tc.Name, func(t *testing.T) {
got := tc.AUM.Weight(tc.State)
if got != tc.Want {
t.Errorf("Weight() = %d, want %d", got, tc.Want)
}
})
}
}
func TestAUMHashes(t *testing.T) {
// .Hash(): a hash over everything.
// .SigHash(): a hash over everything except the signatures.
// The signatures are over a hash of the AUM, so
// using SigHash() breaks this circularity.
aum := AUM{MessageKind: AUMAddKey, Key: &Key{Kind: Key25519}}
sigHash1 := aum.SigHash()
aumHash1 := aum.Hash()
aum.Signatures = []tkatype.Signature{{KeyID: []byte{1, 2, 3, 4}}}
sigHash2 := aum.SigHash()
aumHash2 := aum.Hash()
if len(aum.Signatures) != 1 {
t.Error("signature was removed by one of the hash functions")
}
if !bytes.Equal(sigHash1[:], sigHash1[:]) {
t.Errorf("signature hash dependent on signatures!\n\t1 = %x\n\t2 = %x", sigHash1, sigHash2)
}
if bytes.Equal(aumHash1[:], aumHash2[:]) {
t.Error("aum hash didnt change")
}
}