| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build !ts_omit_tailnetlock
- package tka
- import (
- "bytes"
- "encoding/hex"
- "errors"
- "testing"
- "github.com/fxamacker/cbor/v2"
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- )
- func fromHex(in string) []byte {
- out, err := hex.DecodeString(in)
- if err != nil {
- panic(err)
- }
- return out
- }
- func hashFromHex(in string) *AUMHash {
- var out AUMHash
- copy(out[:], fromHex(in))
- return &out
- }
- func TestCloneState(t *testing.T) {
- tcs := []struct {
- Name string
- State State
- }{
- {
- "Empty",
- State{},
- },
- {
- "Key",
- State{
- 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{
- DisablementSecrets: [][]byte{
- {1, 2, 3, 4},
- {5, 6, 7, 8},
- },
- },
- },
- }
- for _, tc := range tcs {
- t.Run(tc.Name, func(t *testing.T) {
- if diff := cmp.Diff(tc.State, tc.State.Clone()); diff != "" {
- t.Errorf("output state differs (-want, +got):\n%s", diff)
- }
- // Make sure the cloned State is the same even after
- // an encode + decode into + from CBOR.
- t.Run("cbor", func(t *testing.T) {
- out := bytes.NewBuffer(nil)
- encoder, err := cbor.CTAP2EncOptions().EncMode()
- if err != nil {
- t.Fatal(err)
- }
- if err := encoder.NewEncoder(out).Encode(tc.State.Clone()); err != nil {
- t.Fatal(err)
- }
- var decodedState State
- if err := cbor.Unmarshal(out.Bytes(), &decodedState); err != nil {
- t.Fatalf("Unmarshal failed: %v", err)
- }
- if diff := cmp.Diff(tc.State, decodedState); diff != "" {
- t.Errorf("decoded state differs (-want, +got):\n%s", diff)
- }
- })
- })
- }
- }
- func TestApplyUpdatesChain(t *testing.T) {
- intOne := uint(1)
- tcs := []struct {
- Name string
- Updates []AUM
- Start State
- End State
- }{
- {
- "AddKey",
- []AUM{{MessageKind: AUMAddKey, Key: &Key{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}},
- State{},
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- LastAUMHash: hashFromHex("53898e4311d0b6087fcbb871563868a16c629d9267df851fcfa7b52b31d2bd03"),
- },
- },
- {
- "RemoveKey",
- []AUM{{MessageKind: AUMRemoveKey, KeyID: []byte{1, 2, 3, 4}, PrevAUMHash: fromHex("53898e4311d0b6087fcbb871563868a16c629d9267df851fcfa7b52b31d2bd03")}},
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- LastAUMHash: hashFromHex("53898e4311d0b6087fcbb871563868a16c629d9267df851fcfa7b52b31d2bd03"),
- },
- State{
- LastAUMHash: hashFromHex("15d65756abfafbb592279503f40759898590c9c59056be1e2e9f02684c15ba4b"),
- },
- },
- {
- "UpdateKey",
- []AUM{{MessageKind: AUMUpdateKey, KeyID: []byte{1, 2, 3, 4}, Votes: &intOne, Meta: map[string]string{"a": "b"}, PrevAUMHash: fromHex("53898e4311d0b6087fcbb871563868a16c629d9267df851fcfa7b52b31d2bd03")}},
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- LastAUMHash: hashFromHex("53898e4311d0b6087fcbb871563868a16c629d9267df851fcfa7b52b31d2bd03"),
- },
- State{
- LastAUMHash: hashFromHex("d55458a9c3ed6997439ba5a18b9b62d2c6e5e0c1bb4c61409e92a1281a3b458d"),
- Keys: []Key{{Kind: Key25519, Votes: 1, Meta: map[string]string{"a": "b"}, Public: []byte{1, 2, 3, 4}}},
- },
- },
- {
- "ChainedKeyUpdates",
- []AUM{
- {MessageKind: AUMAddKey, Key: &Key{Kind: Key25519, Public: []byte{5, 6, 7, 8}}},
- {MessageKind: AUMRemoveKey, KeyID: []byte{1, 2, 3, 4}, PrevAUMHash: fromHex("f09bda3bb7cf6756ea9adc25770aede4b3ca8142949d6ef5ca0add29af912fd4")},
- },
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- },
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{5, 6, 7, 8}}},
- LastAUMHash: hashFromHex("218165fe5f757304b9deaff4ac742890364f5f509e533c74e80e0ce35e44ee1d"),
- },
- },
- {
- "Checkpoint",
- []AUM{
- {MessageKind: AUMAddKey, Key: &Key{Kind: Key25519, Public: []byte{5, 6, 7, 8}}},
- {MessageKind: AUMCheckpoint, State: &State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- }, PrevAUMHash: fromHex("f09bda3bb7cf6756ea9adc25770aede4b3ca8142949d6ef5ca0add29af912fd4")},
- },
- State{DisablementSecrets: [][]byte{{1, 2, 3, 4}}},
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- LastAUMHash: hashFromHex("57343671da5eea3cfb502954e976e8028bffd3540b50a043b2a65a8d8d8217d0"),
- },
- },
- }
- for _, tc := range tcs {
- t.Run(tc.Name, func(t *testing.T) {
- state := tc.Start
- for i := range tc.Updates {
- var err error
- // t.Logf("update[%d] start-state = %+v", i, state)
- state, err = state.applyVerifiedAUM(tc.Updates[i])
- if err != nil {
- t.Fatalf("Apply message[%d] failed: %v", i, err)
- }
- // t.Logf("update[%d] end-state = %+v", i, state)
- updateHash := tc.Updates[i].Hash()
- if got, want := *state.LastAUMHash, updateHash[:]; !bytes.Equal(got[:], want) {
- t.Errorf("expected state.LastAUMHash = %x (update %d), got %x", want, i, got)
- }
- }
- if diff := cmp.Diff(tc.End, state, cmpopts.EquateEmpty()); diff != "" {
- t.Errorf("output state differs (+got, -want):\n%s", diff)
- }
- })
- }
- }
- func TestApplyUpdateErrors(t *testing.T) {
- tooLargeVotes := uint(99999)
- tcs := []struct {
- Name string
- Updates []AUM
- Start State
- Error error
- }{
- {
- "AddKey exists",
- []AUM{{MessageKind: AUMAddKey, Key: &Key{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}},
- State{Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}},
- errors.New("key already exists"),
- },
- {
- "RemoveKey notfound",
- []AUM{{MessageKind: AUMRemoveKey, Key: &Key{Kind: Key25519, Public: []byte{1, 2, 3, 4}}}},
- State{},
- ErrNoSuchKey,
- },
- {
- "UpdateKey notfound",
- []AUM{{MessageKind: AUMUpdateKey, KeyID: []byte{1}}},
- State{},
- ErrNoSuchKey,
- },
- {
- "UpdateKey now fails validation",
- []AUM{{MessageKind: AUMUpdateKey, KeyID: []byte{1}, Votes: &tooLargeVotes}},
- State{Keys: []Key{{Kind: Key25519, Public: []byte{1}}}},
- errors.New("updated key fails validation: excessive key weight: 99999 > 4096"),
- },
- {
- "Bad lastAUMHash",
- []AUM{
- {MessageKind: AUMAddKey, Key: &Key{Kind: Key25519, Public: []byte{5, 6, 7, 8}}},
- {MessageKind: AUMRemoveKey, KeyID: []byte{1, 2, 3, 4}, PrevAUMHash: fromHex("1234")},
- },
- State{
- Keys: []Key{{Kind: Key25519, Public: []byte{1, 2, 3, 4}}},
- },
- 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 {
- t.Run(tc.Name, func(t *testing.T) {
- state := tc.Start
- for i := range tc.Updates {
- var err error
- // t.Logf("update[%d] start-state = %+v", i, state)
- state, err = state.applyVerifiedAUM(tc.Updates[i])
- if err != nil {
- if err.Error() != tc.Error.Error() {
- t.Errorf("state[%d].Err = %v, want %v", i, err, tc.Error)
- } else {
- return
- }
- }
- // t.Logf("update[%d] end-state = %+v", i, state)
- }
- t.Errorf("did not error, expected %v", tc.Error)
- })
- }
- }
|