diff options
Diffstat (limited to 'pkg/state')
| -rw-r--r-- | pkg/state/mocks/signer.go | 23 | ||||
| -rw-r--r-- | pkg/state/mocks/state_manager.go | 107 | ||||
| -rw-r--r-- | pkg/state/single.go | 156 | ||||
| -rw-r--r-- | pkg/state/single_test.go (renamed from pkg/state/state_manager_test.go) | 233 | ||||
| -rw-r--r-- | pkg/state/state_manager.go | 148 | 
5 files changed, 412 insertions, 255 deletions
| diff --git a/pkg/state/mocks/signer.go b/pkg/state/mocks/signer.go new file mode 100644 index 0000000..7c699dd --- /dev/null +++ b/pkg/state/mocks/signer.go @@ -0,0 +1,23 @@ +package mocks + +import ( +	"crypto" +	"crypto/ed25519" +	"io" +) + +// TestSign implements the signer interface.  It can be used to mock an Ed25519 +// signer that always return the same public key, signature, and error. +type TestSigner struct { +	PublicKey [ed25519.PublicKeySize]byte +	Signature [ed25519.SignatureSize]byte +	Error     error +} + +func (ts *TestSigner) Public() crypto.PublicKey { +	return ed25519.PublicKey(ts.PublicKey[:]) +} + +func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { +	return ts.Signature[:], ts.Error +} diff --git a/pkg/state/mocks/state_manager.go b/pkg/state/mocks/state_manager.go new file mode 100644 index 0000000..009e20f --- /dev/null +++ b/pkg/state/mocks/state_manager.go @@ -0,0 +1,107 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: git.sigsum.org/sigsum-log-go/pkg/state (interfaces: StateManager) + +// Package mocks is a generated GoMock package. +package mocks + +import ( +	context "context" +	reflect "reflect" + +	types "git.sigsum.org/sigsum-lib-go/pkg/types" +	gomock "github.com/golang/mock/gomock" +) + +// MockStateManager is a mock of StateManager interface. +type MockStateManager struct { +	ctrl     *gomock.Controller +	recorder *MockStateManagerMockRecorder +} + +// MockStateManagerMockRecorder is the mock recorder for MockStateManager. +type MockStateManagerMockRecorder struct { +	mock *MockStateManager +} + +// NewMockStateManager creates a new mock instance. +func NewMockStateManager(ctrl *gomock.Controller) *MockStateManager { +	mock := &MockStateManager{ctrl: ctrl} +	mock.recorder = &MockStateManagerMockRecorder{mock} +	return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder { +	return m.recorder +} + +// AddCosignature mocks base method. +func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *types.PublicKey, arg2 *types.Signature) error { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "AddCosignature", arg0, arg1, arg2) +	ret0, _ := ret[0].(error) +	return ret0 +} + +// AddCosignature indicates an expected call of AddCosignature. +func (mr *MockStateManagerMockRecorder) AddCosignature(arg0, arg1, arg2 interface{}) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCosignature", reflect.TypeOf((*MockStateManager)(nil).AddCosignature), arg0, arg1, arg2) +} + +// Cosigned mocks base method. +func (m *MockStateManager) Cosigned(arg0 context.Context) (*types.CosignedTreeHead, error) { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "Cosigned", arg0) +	ret0, _ := ret[0].(*types.CosignedTreeHead) +	ret1, _ := ret[1].(error) +	return ret0, ret1 +} + +// Cosigned indicates an expected call of Cosigned. +func (mr *MockStateManagerMockRecorder) Cosigned(arg0 interface{}) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cosigned", reflect.TypeOf((*MockStateManager)(nil).Cosigned), arg0) +} + +// Latest mocks base method. +func (m *MockStateManager) Latest(arg0 context.Context) (*types.SignedTreeHead, error) { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "Latest", arg0) +	ret0, _ := ret[0].(*types.SignedTreeHead) +	ret1, _ := ret[1].(error) +	return ret0, ret1 +} + +// Latest indicates an expected call of Latest. +func (mr *MockStateManagerMockRecorder) Latest(arg0 interface{}) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Latest", reflect.TypeOf((*MockStateManager)(nil).Latest), arg0) +} + +// Run mocks base method. +func (m *MockStateManager) Run(arg0 context.Context) { +	m.ctrl.T.Helper() +	m.ctrl.Call(m, "Run", arg0) +} + +// Run indicates an expected call of Run. +func (mr *MockStateManagerMockRecorder) Run(arg0 interface{}) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStateManager)(nil).Run), arg0) +} + +// ToSign mocks base method. +func (m *MockStateManager) ToSign(arg0 context.Context) (*types.SignedTreeHead, error) { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "ToSign", arg0) +	ret0, _ := ret[0].(*types.SignedTreeHead) +	ret1, _ := ret[1].(error) +	return ret0, ret1 +} + +// ToSign indicates an expected call of ToSign. +func (mr *MockStateManagerMockRecorder) ToSign(arg0 interface{}) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToSign", reflect.TypeOf((*MockStateManager)(nil).ToSign), arg0) +} diff --git a/pkg/state/single.go b/pkg/state/single.go new file mode 100644 index 0000000..d02b86f --- /dev/null +++ b/pkg/state/single.go @@ -0,0 +1,156 @@ +package state + +import ( +	"context" +	"crypto" +	"crypto/ed25519" +	"fmt" +	"reflect" +	"sync" +	"time" + +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db" +	"github.com/golang/glog" +	"github.com/google/certificate-transparency-go/schedule" +) + +// StateManagerSingle implements a single-instance StateManager.  In other +// words, there's no other state that needs to be synced on any remote machine. +type StateManagerSingle struct { +	client   db.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.CosignedTreeHead + +	// toSign is the current tree head that is being cosigned by witnesses +	toSign types.SignedTreeHead + +	// cosignatures keep track of all cosignatures for the toSign tree head +	cosignatures map[types.Hash]*types.Signature +} + +func NewStateManagerSingle(client db.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { +	sm := &StateManagerSingle{ +		client:   client, +		signer:   signer, +		interval: interval, +		deadline: deadline, +	} +	ctx, cancel := context.WithTimeout(context.Background(), sm.deadline) +	defer cancel() + +	sth, err := sm.Latest(ctx) +	if err != nil { +		return nil, fmt.Errorf("Latest: %v", err) +	} +	sm.toSign = *sth +	sm.cosignatures = make(map[types.Hash]*types.Signature) +	sm.cosigned = types.CosignedTreeHead{ +		SignedTreeHead: *sth, +		Cosignature:    make([]types.Signature, 0), +		KeyHash:        make([]types.Hash, 0), +	} +	return sm, nil +} + +func (sm *StateManagerSingle) Run(ctx context.Context) { +	schedule.Every(ctx, sm.interval, func(ctx context.Context) { +		ictx, cancel := context.WithTimeout(ctx, sm.deadline) +		defer cancel() + +		nextSTH, err := sm.Latest(ictx) +		if err != nil { +			glog.Warningf("rotate failed: Latest: %v", err) +			return +		} + +		sm.Lock() +		defer sm.Unlock() +		sm.rotate(nextSTH) +	}) +} + +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) +	} + +	namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey)) +	return th.Sign(sm.signer, namespace) +} + +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.CosignedTreeHead, error) { +	sm.RLock() +	defer sm.RUnlock() +	if len(sm.cosigned.Cosignature) == 0 { +		return nil, fmt.Errorf("no witness cosignatures available") +	} +	return &sm.cosigned, nil +} + +func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *types.PublicKey, sig *types.Signature) error { +	sm.Lock() +	defer sm.Unlock() + +	namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey)) +	msg := sm.toSign.TreeHead.ToBinary(namespace) +	if !ed25519.Verify(ed25519.PublicKey(vk[:]), msg, sig[:]) { +		return fmt.Errorf("invalid tree head signature") +	} + +	witness := types.HashFn(vk[:]) +	if _, ok := sm.cosignatures[*witness]; ok { +		return fmt.Errorf("signature-signer pair is a duplicate") // TODO: maybe not an error +	} +	sm.cosignatures[*witness] = sig + +	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.SignedTreeHead, sm.toSign) { +		// cosigned and toSign are the same.  So, we need to merge all +		// cosignatures that we already had with the new collected ones. +		for i := 0; i < len(sm.cosigned.Cosignature); i++ { +			kh := sm.cosigned.KeyHash[i] +			sig := sm.cosigned.Cosignature[i] + +			if _, ok := sm.cosignatures[kh]; !ok { +				sm.cosignatures[kh] = &sig +			} +		} +		glog.V(3).Infof("cosigned tree head repeated, merged signatures") +	} +	var cosignatures []types.Signature +	var keyHashes []types.Hash + +	for keyHash, cosignature := range sm.cosignatures { +		cosignatures = append(cosignatures, *cosignature) +		keyHashes = append(keyHashes, keyHash) +	} + +	// Update cosigned tree head +	sm.cosigned.SignedTreeHead = sm.toSign +	sm.cosigned.Cosignature = cosignatures +	sm.cosigned.KeyHash = keyHashes + +	// Update to-sign tree head +	sm.toSign = *next +	sm.cosignatures = make(map[types.Hash]*types.Signature, 0) // TODO: on repeat we might want to not zero this +	glog.V(3).Infof("rotated tree heads") +} diff --git a/pkg/state/state_manager_test.go b/pkg/state/single_test.go index f11ba28..b315b3e 100644 --- a/pkg/state/state_manager_test.go +++ b/pkg/state/single_test.go @@ -11,36 +11,34 @@ import (  	"testing"  	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	mocksTrillian "git.sigsum.org/sigsum-log-go/pkg/db/mocks" +	mocksSigner "git.sigsum.org/sigsum-log-go/pkg/state/mocks"  	"github.com/golang/mock/gomock" -	"git.sigsum.org/sigsum-log-go/pkg/mocks" -	"git.sigsum.org/sigsum-log-go/pkg/types"  )  var ( -	testSig = &[types.SignatureSize]byte{} -	testPub = &[types.VerificationKeySize]byte{} -	testTH  = &types.TreeHead{ +	testTH = types.TreeHead{  		Timestamp: 0,  		TreeSize:  0, -		RootHash:  types.Hash(nil), -		KeyHash:   types.Hash(testPub[:]), +		RootHash:  types.Hash{},  	} -	testSigIdent = &types.SigIdent{ -		Signature: testSig, -		KeyHash:   types.Hash(testPub[:]), +	testSTH = types.SignedTreeHead{ +		TreeHead:  testTH, +		Signature: types.Signature{},  	} -	testSTH = &types.SignedTreeHead{ -		TreeHead:  *testTH, -		Signature: testSig, -	} -	testCTH = &types.CosignedTreeHead{ -		SignedTreeHead: *testSTH, -		SigIdent: []*types.SigIdent{ -			testSigIdent, +	testCTH = types.CosignedTreeHead{ +		SignedTreeHead: testSTH, +		Cosignature: []types.Signature{ +			types.Signature{}, +		}, +		KeyHash: []types.Hash{ +			types.Hash{},  		},  	} -	testSignerOK  = &mocks.TestSigner{testPub, testSig, nil} -	testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} + +	testSignerOK  = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, nil} +	testSignerErr = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, fmt.Errorf("something went wrong")}  )  func TestNewStateManagerSingle(t *testing.T) { @@ -61,15 +59,15 @@ func TestNewStateManagerSingle(t *testing.T) {  		{  			description: "valid",  			signer:      testSignerOK, -			rsp:         testTH, -			wantSth:     testSTH, +			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 := mocksTrillian.NewMockClient(ctrl)  			client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)  			sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) @@ -82,11 +80,11 @@ func TestNewStateManagerSingle(t *testing.T) {  			if got, want := &sm.cosigned.SignedTreeHead, 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) +			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), 0; got != want { +			if got, want := len(sm.cosignatures), 0; got != want {  				t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description)  			}  		}() @@ -110,22 +108,22 @@ func TestLatest(t *testing.T) {  		},  		{  			description: "invalid: signature failure", -			rsp:         testTH, +			rsp:         &testTH,  			signer:      testSignerErr,  			wantErr:     true,  		},  		{  			description: "valid",  			signer:      testSignerOK, -			rsp:         testTH, -			wantSth:     testSTH, +			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 := mocksTrillian.NewMockClient(ctrl)  			client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)  			sm := StateManagerSingle{  				client: client, @@ -149,14 +147,14 @@ func TestLatest(t *testing.T) {  func TestToSign(t *testing.T) {  	description := "valid"  	sm := StateManagerSingle{ -		tosign: *testSTH, +		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) { +	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)  	}  } @@ -164,18 +162,19 @@ func TestToSign(t *testing.T) {  func TestCosigned(t *testing.T) {  	description := "valid"  	sm := StateManagerSingle{ -		cosigned: *testCTH, +		cosigned: testCTH,  	}  	cth, err := sm.Cosigned(context.Background())  	if err != nil {  		t.Errorf("Cosigned should not fail with error: %v", err)  		return  	} -	if got, want := cth, testCTH; !reflect.DeepEqual(got, want) { +	if got, want := cth, &testCTH; !reflect.DeepEqual(got, want) {  		t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description)  	} -	sm.cosigned.SigIdent = make([]*types.SigIdent, 0) +	sm.cosigned.Cosignature = make([]types.Signature, 0) +	sm.cosigned.KeyHash = make([]types.Hash, 0)  	cth, err = sm.Cosigned(context.Background())  	if err == nil {  		t.Errorf("Cosigned should fail without witness cosignatures") @@ -188,57 +187,55 @@ func TestAddCosignature(t *testing.T) {  	if err != nil {  		t.Fatalf("GenerateKey: %v", err)  	} -	if bytes.Equal(vk[:], testPub[:]) { +	if bytes.Equal(vk[:], new(types.PublicKey)[:]) {  		t.Fatalf("Sampled same key as testPub, aborting...")  	} -	var vkArray [types.VerificationKeySize]byte +	var vkArray types.PublicKey  	copy(vkArray[:], vk[:])  	for _, table := range []struct {  		description string  		signer      crypto.Signer -		vk          *[types.VerificationKeySize]byte -		th          *types.TreeHead +		vk          types.PublicKey +		th          types.TreeHead  		wantErr     bool  	}{  		{  			description: "invalid: signature error",  			signer:      sk, -			vk:          testPub, // wrong key for message +			vk:          types.PublicKey{}, // wrong key for message  			th:          testTH,  			wantErr:     true,  		},  		{  			description: "valid",  			signer:      sk, -			vk:          &vkArray, +			vk:          vkArray,  			th:          testTH,  		},  	} { -		sth, _ := table.th.Sign(testSignerOK) +		kh := types.HashFn(testSignerOK.Public().(ed25519.PublicKey)) +		sth := mustSign(t, testSignerOK, &table.th, kh)  		cth := &types.CosignedTreeHead{  			SignedTreeHead: *sth, -			SigIdent:       []*types.SigIdent{}, +			Cosignature:    make([]types.Signature, 0), +			KeyHash:        make([]types.Hash, 0),  		}  		sm := &StateManagerSingle{ -			signer:      testSignerOK, -			cosigned:    *cth, -			tosign:      *sth, -			cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +			signer:       testSignerOK, +			cosigned:     *cth, +			toSign:       *sth, +			cosignatures: make(map[types.Hash]*types.Signature, 0),  		}  		// Prepare witness signature -		sth, err := table.th.Sign(table.signer) -		if err != nil { -			t.Fatalf("Sign: %v", err) -		} -		si := &types.SigIdent{ -			KeyHash:   types.Hash(table.signer.Public().(ed25519.PublicKey)[:]), -			Signature: sth.Signature, -		} +		var vk types.PublicKey +		copy(vk[:], table.vk[:]) //table.signer.Public().(ed25519.PublicKey)) +		sth = mustSign(t, table.signer, &table.th, kh) +		kh = types.HashFn(vk[:])  		// Add witness signature -		err = sm.AddCosignature(context.Background(), table.vk, si.Signature) +		err = sm.AddCosignature(context.Background(), &vk, &sth.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)  		} @@ -247,48 +244,47 @@ func TestAddCosignature(t *testing.T) {  		}  		// We should have one witness signature -		if got, want := len(sm.cosignature), 1; got != want { +		if got, want := len(sm.cosignatures), 1; got != want {  			t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description)  			continue  		}  		// check that witness signature is there -		sigident, ok := sm.cosignature[*si.KeyHash] +		sig, ok := sm.cosignatures[*kh]  		if !ok {  			t.Errorf("witness signature is missing")  			continue  		} -		if got, want := si, sigident; !reflect.DeepEqual(got, want) { +		if got, want := sig[:], sth.Signature[:]; !bytes.Equal(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, si.Signature); err == nil { +		if err := sm.AddCosignature(context.Background(), &vk, &sth.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 +	logSig := types.Signature{} +	wit1Sig := types.Signature{} +	wit2Sig := types.Signature{} + +	//logKH := &types.Hash{} +	wit1KH := types.HashFn([]byte("wit1 key")) +	wit2KH := types.HashFn([]byte("wit2 key")) + +	th0 := &testTH  	th1 := &types.TreeHead{  		Timestamp: 1,  		TreeSize:  1, -		RootHash:  types.Hash([]byte("1")), +		RootHash:  *types.HashFn([]byte("1")),  	}  	th2 := &types.TreeHead{  		Timestamp: 2,  		TreeSize:  2, -		RootHash:  types.Hash([]byte("2")), +		RootHash:  *types.HashFn([]byte("2")),  	}  	for _, table := range []struct { @@ -297,77 +293,81 @@ func TestRotate(t *testing.T) {  		next          *types.SignedTreeHead  	}{  		{ -			description: "tosign tree head repated, but got one new witnes signature", +			description: "toSign tree head repated, but got one new witnes signature",  			before: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1}, +					Cosignature: []types.Signature{wit1Sig}, +					KeyHash:     []types.Hash{*wit1KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th0, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{ -					*wit2.KeyHash: wit2, // the new witness signature +				cosignatures: map[types.Hash]*types.Signature{ +					*wit2KH: &wit2Sig, // the new witness signature  				},  			},  			next: &types.SignedTreeHead{  				TreeHead:  *th1, -				Signature: log.Signature, +				Signature: logSig,  			},  			after: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1, wit2}, +					Cosignature: []types.Signature{wit1Sig, wit2Sig}, +					KeyHash:     []types.Hash{*wit1KH, *wit2KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th1, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +				cosignatures: map[types.Hash]*types.Signature{},  			},  		},  		{ -			description: "tosign tree head did not repeat, it got one witness signature", +			description: "toSign tree head did not repeat, it got one witness signature",  			before: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1}, +					Cosignature: []types.Signature{wit1Sig}, +					KeyHash:     []types.Hash{*wit1KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th1, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{ -					*log.KeyHash: wit2, +				cosignatures: map[types.Hash]*types.Signature{ +					*wit2KH: &wit2Sig,  				},  			},  			next: &types.SignedTreeHead{  				TreeHead:  *th2, -				Signature: log.Signature, +				Signature: logSig,  			},  			after: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th1, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit2}, +					Cosignature: []types.Signature{wit2Sig}, +					KeyHash:     []types.Hash{*wit2KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th2, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +				cosignatures: map[types.Hash]*types.Signature{},  			},  		},  	} { @@ -375,31 +375,44 @@ func TestRotate(t *testing.T) {  		if got, want := table.before.cosigned.SignedTreeHead, table.after.cosigned.SignedTreeHead; !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, table.after.tosign; !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.cosigned, table.after.cosigned) +		if got, want := table.before.toSign, table.after.toSign; !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)  		} -		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) +		if got, want := table.before.cosignatures, table.after.cosignatures; !reflect.DeepEqual(got, want) { +			t.Errorf("got cosignatures 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) { +func checkWitnessList(t *testing.T, description string, got, want types.CosignedTreeHead) {  	t.Helper() -	for _, si := range got { +	if got, want := len(got.Cosignature), len(want.Cosignature); got != want { +		t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, description) +		return +	} +	if got, want := len(got.KeyHash), len(want.KeyHash); got != want { +		t.Errorf("got %d key hashes but wanted %d in test %q", got, want, description) +		return +	} +	for i := 0; i < len(got.Cosignature); i++ {  		found := false -		for _, sj := range want { -			if reflect.DeepEqual(si, sj) { +		for j := 0; j < len(want.Cosignature); j++ { +			if bytes.Equal(got.Cosignature[i][:], want.Cosignature[j][:]) && bytes.Equal(got.KeyHash[i][:], want.KeyHash[j][:]) {  				found = true  				break  			}  		}  		if !found { -			t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) +			t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, got.KeyHash[i][:])  		}  	} -	if len(got) != len(want) { -		t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) +} + +func mustSign(t *testing.T, s crypto.Signer, th *types.TreeHead, kh *types.Hash) *types.SignedTreeHead { +	sth, err := th.Sign(s, kh) +	if err != nil { +		t.Fatal(err)  	} +	return sth  } diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go index 84431be..5aa7609 100644 --- a/pkg/state/state_manager.go +++ b/pkg/state/state_manager.go @@ -2,157 +2,15 @@ package state  import (  	"context" -	"crypto" -	"crypto/ed25519" -	"fmt" -	"reflect" -	"sync" -	"time" -	"github.com/golang/glog" -	"github.com/google/certificate-transparency-go/schedule" -	"git.sigsum.org/sigsum-log-go/pkg/trillian" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  ) -// StateManager coordinates access to the log's tree heads and (co)signatures +// StateManager coordinates access to a 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.CosignedTreeHead, error) -	AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error +	AddCosignature(context.Context, *types.PublicKey, *types.Signature) 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.CosignedTreeHead - -	// 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 = types.CosignedTreeHead{ -		SignedTreeHead: *sth, -		SigIdent:       []*types.SigIdent{}, -	} -	sm.tosign = *sth -	sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{} -	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) -		nextSTH, err := sm.Latest(ictx) -		if err != nil { -			glog.Warningf("rotate failed: Latest: %v", err) -			return -		} - -		sm.Lock() -		defer sm.Unlock() -		sm.rotate(nextSTH) -	}) -} - -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) -	} -	th.KeyHash = types.Hash(sm.signer.Public().(ed25519.PublicKey)[:]) -	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.CosignedTreeHead, error) { -	sm.RLock() -	defer sm.RUnlock() -	if len(sm.cosigned.SigIdent) == 0 { -		return nil, fmt.Errorf("no witness cosignatures available") -	} -	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.SignedTreeHead, sm.tosign) { -		// 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.SignedTreeHead = sm.tosign -	sm.cosigned.SigIdent = cosignatures - -	// Update to-sign tree head -	sm.tosign = *next -	sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{} // TODO: on repeat we might want to not zero this -	glog.V(3).Infof("rotated tree heads") -} | 
