diff options
Diffstat (limited to 'state')
-rw-r--r-- | state/state_manager.go | 154 | ||||
-rw-r--r-- | state/state_manager_test.go | 393 |
2 files changed, 0 insertions, 547 deletions
diff --git a/state/state_manager.go b/state/state_manager.go deleted file mode 100644 index 3199e61..0000000 --- a/state/state_manager.go +++ /dev/null @@ -1,154 +0,0 @@ -package stfe - -import ( - "context" - "crypto" - "fmt" - "reflect" - "sync" - "time" - - "github.com/golang/glog" - "github.com/google/certificate-transparency-go/schedule" - "github.com/system-transparency/stfe/trillian" - "github.com/system-transparency/stfe/types" -) - -// StateManager coordinates access to the log's tree heads and (co)signatures -type StateManager interface { - Latest(context.Context) (*types.SignedTreeHead, error) - ToSign(context.Context) (*types.SignedTreeHead, error) - Cosigned(context.Context) (*types.SignedTreeHead, error) - AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error - Run(context.Context) -} - -// StateManagerSingle implements the StateManager interface. It is assumed that -// the log server is running on a single-instance machine. So, no coordination. -type StateManagerSingle struct { - client trillian.Client - signer crypto.Signer - interval time.Duration - deadline time.Duration - sync.RWMutex - - // cosigned is the current cosigned tree head that is being served - cosigned types.SignedTreeHead - - // tosign is the current tree head that is being cosigned by witnesses - tosign types.SignedTreeHead - - // cosignature keeps track of all cosignatures for the tosign tree head - cosignature map[[types.HashSize]byte]*types.SigIdent -} - -func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { - sm := &StateManagerSingle{ - client: client, - signer: signer, - interval: interval, - deadline: deadline, - } - - ctx, _ := context.WithTimeout(context.Background(), sm.deadline) - sth, err := sm.Latest(ctx) - if err != nil { - return nil, fmt.Errorf("Latest: %v", err) - } - - sm.cosigned = *sth - sm.tosign = *sth - sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ - *sth.SigIdent[0].KeyHash: sth.SigIdent[0], // log signature - } - return sm, nil -} - -func (sm *StateManagerSingle) Run(ctx context.Context) { - schedule.Every(ctx, sm.interval, func(ctx context.Context) { - ictx, _ := context.WithTimeout(ctx, sm.deadline) - nextTreeHead, err := sm.Latest(ictx) - if err != nil { - glog.Warningf("rotate failed: Latest: %v", err) - return - } - - sm.Lock() - defer sm.Unlock() - sm.rotate(nextTreeHead) - }) -} - -func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { - th, err := sm.client.GetTreeHead(ctx) - if err != nil { - return nil, fmt.Errorf("LatestTreeHead: %v", err) - } - sth, err := th.Sign(sm.signer) - if err != nil { - return nil, fmt.Errorf("sign: %v", err) - } - return sth, nil -} - -func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { - sm.RLock() - defer sm.RUnlock() - return &sm.tosign, nil -} - -func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.SignedTreeHead, error) { - sm.RLock() - defer sm.RUnlock() - return &sm.cosigned, nil -} - -func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error { - sm.Lock() - defer sm.Unlock() - - if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil { - return fmt.Errorf("Verify: %v", err) - } - witness := types.Hash(vk[:]) - if _, ok := sm.cosignature[*witness]; ok { - return fmt.Errorf("signature-signer pair is a duplicate") - } - sm.cosignature[*witness] = &types.SigIdent{ - Signature: sig, - KeyHash: witness, - } - - glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) - return nil -} - -// rotate rotates the log's cosigned and stable STH. The caller must aquire the -// source's read-write lock if there are concurrent reads and/or writes. -func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { - if reflect.DeepEqual(sm.cosigned.TreeHead, sm.tosign.TreeHead) { - // cosigned and tosign are the same. So, we need to merge all - // cosignatures that we already had with the new collected ones. - for _, sigident := range sm.cosigned.SigIdent { - if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { - sm.cosignature[*sigident.KeyHash] = sigident - } - } - glog.V(3).Infof("cosigned tree head repeated, merged signatures") - } - var cosignatures []*types.SigIdent - for _, sigident := range sm.cosignature { - cosignatures = append(cosignatures, sigident) - } - - // Update cosigned tree head - sm.cosigned.TreeHead = sm.tosign.TreeHead - sm.cosigned.SigIdent = cosignatures - - // Update to-sign tree head - sm.tosign = *next - sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{ - *next.SigIdent[0].KeyHash: next.SigIdent[0], // log signature - } - glog.V(3).Infof("rotated tree heads") -} diff --git a/state/state_manager_test.go b/state/state_manager_test.go deleted file mode 100644 index 348074c..0000000 --- a/state/state_manager_test.go +++ /dev/null @@ -1,393 +0,0 @@ -package stfe - -import ( - "bytes" - "context" - "crypto" - "crypto/ed25519" - "crypto/rand" - "fmt" - "reflect" - "testing" - "time" - - "github.com/golang/mock/gomock" - "github.com/system-transparency/stfe/mocks" - "github.com/system-transparency/stfe/types" -) - -var ( - testSig = &[types.SignatureSize]byte{} - testPub = &[types.VerificationKeySize]byte{} - testTH = &types.TreeHead{ - Timestamp: 0, - TreeSize: 0, - RootHash: types.Hash(nil), - } - testSigIdent = &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash(testPub[:]), - } - testSTH = &types.SignedTreeHead{ - TreeHead: *testTH, - SigIdent: []*types.SigIdent{testSigIdent}, - } - testSignerOK = &mocks.TestSigner{testPub, testSig, nil} - testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} -) - -func TestNewStateManagerSingle(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - rsp *types.TreeHead - err error - wantErr bool - wantSth *types.SignedTreeHead - }{ - { - description: "invalid: backend failure", - signer: testSignerOK, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "valid", - signer: testSignerOK, - rsp: testTH, - wantSth: testSTH, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) - - sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := &sm.cosigned, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - // we only have log signature on startup - if got, want := len(sm.cosignature), 1; got != want { - t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description) - } - }() - } -} - -func TestLatest(t *testing.T) { - for _, table := range []struct { - description string - signer crypto.Signer - rsp *types.TreeHead - err error - wantErr bool - wantSth *types.SignedTreeHead - }{ - { - description: "invalid: backend failure", - signer: testSignerOK, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: signature failure", - rsp: testTH, - signer: testSignerErr, - wantErr: true, - }, - { - description: "valid", - signer: testSignerOK, - rsp: testTH, - wantSth: testSTH, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - client := mocks.NewMockClient(ctrl) - client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err) - sm := StateManagerSingle{ - client: client, - signer: table.signer, - } - - sth, err := sm.Latest(context.Background()) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - return - } - if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestToSign(t *testing.T) { - description := "valid" - sm := StateManagerSingle{ - tosign: *testSTH, - } - sth, err := sm.ToSign(context.Background()) - if err != nil { - t.Errorf("ToSign should not fail with error: %v", err) - return - } - if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) - } -} - -func TestCosigned(t *testing.T) { - description := "valid" - sm := StateManagerSingle{ - cosigned: *testSTH, - } - sth, err := sm.Cosigned(context.Background()) - if err != nil { - t.Errorf("Cosigned should not fail with error: %v", err) - return - } - if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { - t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description) - } -} - -func TestAddCosignature(t *testing.T) { - vk, sk, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("GenerateKey: %v", err) - } - if bytes.Equal(vk[:], testPub[:]) { - t.Fatalf("Sampled same key as testPub, aborting...") - } - var vkArray [types.VerificationKeySize]byte - copy(vkArray[:], vk[:]) - - for _, table := range []struct { - description string - signer crypto.Signer - vk *[types.VerificationKeySize]byte - th *types.TreeHead - wantErr bool - }{ - { - description: "invalid: signature error", - signer: sk, - vk: testPub, // wrong key for message - th: testTH, - wantErr: true, - }, - { - description: "valid", - signer: sk, - vk: &vkArray, - th: testTH, - }, - } { - sth, _ := table.th.Sign(testSignerOK) - logKeyHash := sth.SigIdent[0].KeyHash - logSigIdent := sth.SigIdent[0] - sm := &StateManagerSingle{ - signer: testSignerOK, - cosigned: *sth, - tosign: *sth, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *logKeyHash: logSigIdent, - }, - } - - // Prepare witness signature - sth, err := table.th.Sign(table.signer) - if err != nil { - t.Fatalf("Sign: %v", err) - } - witnessKeyHash := sth.SigIdent[0].KeyHash - witnessSigIdent := sth.SigIdent[0] - - // Add witness signature - err = sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - // We should have two signatures (log + witness) - if got, want := len(sm.cosignature), 2; got != want { - t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description) - continue - } - // check that log signature is there - sigident, ok := sm.cosignature[*logKeyHash] - if !ok { - t.Errorf("log signature is missing") - continue - } - if got, want := sigident, logSigIdent; !reflect.DeepEqual(got, want) { - t.Errorf("got log sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - // check that witness signature is there - sigident, ok = sm.cosignature[*witnessKeyHash] - if !ok { - t.Errorf("witness signature is missing") - continue - } - if got, want := sigident, witnessSigIdent; !reflect.DeepEqual(got, want) { - t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - continue - } - - // Adding a duplicate signature should give an error - if err := sm.AddCosignature(context.Background(), table.vk, witnessSigIdent.Signature); err == nil { - t.Errorf("duplicate witness signature accepted as valid") - } - } -} - -func TestRotate(t *testing.T) { - log := testSigIdent - wit1 := &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash([]byte("wit1 key")), - } - wit2 := &types.SigIdent{ - Signature: testSig, - KeyHash: types.Hash([]byte("wit2 key")), - } - th0 := testTH - th1 := &types.TreeHead{ - Timestamp: 1, - TreeSize: 1, - RootHash: types.Hash([]byte("1")), - } - th2 := &types.TreeHead{ - Timestamp: 2, - TreeSize: 2, - RootHash: types.Hash([]byte("2")), - } - - for _, table := range []struct { - description string - before, after *StateManagerSingle - next *types.SignedTreeHead - }{ - { - description: "tosign tree head repated, but got one new witnes signature", - before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, - *wit2.KeyHash: wit2, // the new witness signature - }, - }, - next: &types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1, wit2}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig - }, - }, - }, - { - description: "tosign tree head did not repeat, it got one witness signature", - before: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th0, - SigIdent: []*types.SigIdent{log, wit1}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, - *wit2.KeyHash: wit2, // the only witness that signed tosign - }, - }, - next: &types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, - }, - after: &StateManagerSingle{ - cosigned: types.SignedTreeHead{ - TreeHead: *th1, - SigIdent: []*types.SigIdent{log, wit2}, - }, - tosign: types.SignedTreeHead{ - TreeHead: *th2, - SigIdent: []*types.SigIdent{log}, - }, - cosignature: map[[types.HashSize]byte]*types.SigIdent{ - *log.KeyHash: log, // after rotate we always have log sig - }, - }, - }, - } { - table.before.rotate(table.next) - if got, want := table.before.cosigned.TreeHead, table.after.cosigned.TreeHead; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent) - if got, want := table.before.tosign.TreeHead, table.after.tosign.TreeHead; !reflect.DeepEqual(got, want) { - t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - checkWitnessList(t, table.description, table.before.tosign.SigIdent, table.after.tosign.SigIdent) - if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) { - t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - } -} - -func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) { - t.Helper() - for _, si := range got { - found := false - for _, sj := range want { - if reflect.DeepEqual(si, sj) { - found = true - break - } - } - if !found { - t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) - } - } - if len(got) != len(want) { - t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) - } -} |