diff options
| author | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 19:53:54 +0100 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 19:53:54 +0100 | 
| commit | dda238b9fc105219f220f0ec3b341b0c81b71301 (patch) | |
| tree | edbbb787ccd1c1816edfa44caf749c8be68b7bf9 /pkg/instance | |
| parent | 5ba4a77233549819440cc41a02503f3a85213e24 (diff) | |
types: Start using sigsum-lib-go
This commit does not change the way in which the log behaves externally.
In other words, all changes are internal and involves renaming and code
restructuring.  Most notably picking up the refactored sigsum-lib-go.
Diffstat (limited to 'pkg/instance')
| -rw-r--r-- | pkg/instance/experimental.go (renamed from pkg/instance/experimental_endpoint.go) | 2 | ||||
| -rw-r--r-- | pkg/instance/handler.go (renamed from pkg/instance/endpoint.go) | 63 | ||||
| -rw-r--r-- | pkg/instance/handler_test.go (renamed from pkg/instance/endpoint_test.go) | 271 | ||||
| -rw-r--r-- | pkg/instance/instance.go | 104 | ||||
| -rw-r--r-- | pkg/instance/instance_test.go | 98 | 
5 files changed, 257 insertions, 281 deletions
| diff --git a/pkg/instance/experimental_endpoint.go b/pkg/instance/experimental.go index 2986a27..ab81ada 100644 --- a/pkg/instance/experimental_endpoint.go +++ b/pkg/instance/experimental.go @@ -11,7 +11,7 @@ import (  	"fmt"  	"net/http" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"github.com/golang/glog"  ) diff --git a/pkg/instance/endpoint.go b/pkg/instance/handler.go index a6d424d..66a20a5 100644 --- a/pkg/instance/endpoint.go +++ b/pkg/instance/handler.go @@ -2,11 +2,58 @@ package instance  import (  	"context" +	"fmt"  	"net/http" +	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"github.com/golang/glog"  ) +// Handler implements the http.Handler interface, and contains a reference +// to a sigsum server instance as well as a function that uses it. +type Handler struct { +	Instance *Instance +	Endpoint types.Endpoint +	Method   string +	Handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) +} + +// Path returns a path that should be configured for this handler +func (h Handler) Path() string { +	if len(h.Instance.Prefix) == 0 { +		return h.Endpoint.Path("", "sigsum", "v0") +	} +	return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0") +} + +// ServeHTTP is part of the http.Handler interface +func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +	// export prometheus metrics +	var now time.Time = time.Now() +	var statusCode int +	defer func() { +		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +	}() +	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) + +	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) +	defer cancel() + +	if r.Method != a.Method { +		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) +		http.Error(w, "", http.StatusMethodNotAllowed) +		return +	} + +	statusCode, err := a.Handler(ctx, a.Instance, w, r) +	if err != nil { +		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) +		http.Error(w, fmt.Sprintf("Error=%s\n", err.Error()), statusCode) +	} +} +  func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.V(3).Info("handling add-entry request")  	req, err := i.leafRequestFromHTTP(ctx, r) @@ -25,8 +72,8 @@ func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *  	if err != nil {  		return http.StatusBadRequest, err  	} -	vk := i.Witnesses[*req.KeyHash] -	if err := i.Stateman.AddCosignature(ctx, &vk, req.Signature); err != nil { +	vk := i.Witnesses[req.KeyHash] +	if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil {  		return http.StatusBadRequest, err  	}  	return http.StatusOK, nil @@ -38,7 +85,7 @@ func getTreeHeadLatest(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := sth.MarshalASCII(w); err != nil { +	if err := sth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -50,7 +97,7 @@ func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := sth.MarshalASCII(w); err != nil { +	if err := sth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -62,7 +109,7 @@ func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := cth.MarshalASCII(w); err != nil { +	if err := cth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -79,7 +126,7 @@ func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := proof.MarshalASCII(w); err != nil { +	if err := proof.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -96,7 +143,7 @@ func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := proof.MarshalASCII(w); err != nil { +	if err := proof.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -114,7 +161,7 @@ func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.  		return http.StatusInternalServerError, err  	}  	for _, leaf := range *leaves { -		if err := leaf.MarshalASCII(w); err != nil { +		if err := leaf.ToASCII(w); err != nil {  			return http.StatusInternalServerError, err  		}  	} diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/handler_test.go index 18a6c27..ba5b60c 100644 --- a/pkg/instance/endpoint_test.go +++ b/pkg/instance/handler_test.go @@ -4,60 +4,137 @@ import (  	"bytes"  	"crypto/ed25519"  	"crypto/rand" -	"encoding/hex"  	"fmt"  	"io"  	"net/http"  	"net/http/httptest" +	"reflect"  	"testing"  	"time" -	"git.sigsum.org/sigsum-log-go/pkg/mocks" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	mocksDB "git.sigsum.org/sigsum-log-go/pkg/db/mocks" +	mocksDNS "git.sigsum.org/sigsum-log-go/pkg/dns/mocks" +	mocksState "git.sigsum.org/sigsum-log-go/pkg/state/mocks"  	"github.com/golang/mock/gomock"  )  var ( -	testWitVK  = [types.VerificationKeySize]byte{} +	testWitVK  = types.PublicKey{}  	testConfig = Config{ -		LogID:      hex.EncodeToString(types.Hash([]byte("logid"))[:]), +		LogID:      fmt.Sprintf("%x", types.HashFn([]byte("logid"))[:]),  		TreeID:     0,  		Prefix:     "testonly",  		MaxRange:   3,  		Deadline:   10,  		Interval:   10,  		ShardStart: 10, -		Witnesses: map[[types.HashSize]byte][types.VerificationKeySize]byte{ -			*types.Hash(testWitVK[:]): testWitVK, +		Witnesses: map[types.Hash]types.PublicKey{ +			*types.HashFn(testWitVK[:]): testWitVK,  		},  	}  	testSTH = &types.SignedTreeHead{  		TreeHead: types.TreeHead{  			Timestamp: 0,  			TreeSize:  0, -			RootHash:  types.Hash(nil), +			RootHash:  *types.HashFn([]byte("root hash")),  		}, -		Signature: &[types.SignatureSize]byte{}, +		Signature: types.Signature{},  	}  	testCTH = &types.CosignedTreeHead{  		SignedTreeHead: *testSTH, -		SigIdent: []*types.SigIdent{ -			&types.SigIdent{ -				KeyHash:   &[types.HashSize]byte{}, -				Signature: &[types.SignatureSize]byte{}, -			}, -		}, +		Cosignature:    []types.Signature{types.Signature{}}, +		KeyHash:        []types.Hash{types.Hash{}},  	}  ) -func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { +// TestHandlers check that the expected handlers are configured +func TestHandlers(t *testing.T) { +	endpoints := map[types.Endpoint]bool{ +		types.EndpointAddLeaf:             false, +		types.EndpointAddCosignature:      false, +		types.EndpointGetTreeHeadLatest:   false, +		types.EndpointGetTreeHeadToSign:   false, +		types.EndpointGetTreeHeadCosigned: false, +		types.EndpointGetConsistencyProof: false, +		types.EndpointGetInclusionProof:   false, +		types.EndpointGetLeaves:           false, +		types.Endpoint("get-checkpoint"):  false, +	} +	i := &Instance{ +		Config: testConfig, +	}  	for _, handler := range i.Handlers() { -		if handler.Endpoint == e { -			return handler +		if _, ok := endpoints[handler.Endpoint]; !ok { +			t.Errorf("got unexpected endpoint: %s", handler.Endpoint) +		} +		endpoints[handler.Endpoint] = true +	} +	for endpoint, ok := range endpoints { +		if !ok { +			t.Errorf("endpoint %s is not configured", endpoint) +		} +	} +} + +// TestServeHTTP checks that invalid HTTP methods are rejected +func TestServeHTTP(t *testing.T) { +	i := &Instance{ +		Config: testConfig, +	} +	for _, handler := range i.Handlers() { +		// Prepare invalid HTTP request +		method := http.MethodPost +		if method == handler.Method { +			method = http.MethodGet +		} +		url := handler.Endpoint.Path("http://example.com", i.Prefix) +		req, err := http.NewRequest(method, url, nil) +		if err != nil { +			t.Fatalf("must create HTTP request: %v", err) +		} +		w := httptest.NewRecorder() + +		// Check that it is rejected +		handler.ServeHTTP(w, req) +		if got, want := w.Code, http.StatusMethodNotAllowed; got != want { +			t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) +		} +	} +} + +// TestPath checks that Path works for an endpoint (add-leaf) +func TestPath(t *testing.T) { +	for _, table := range []struct { +		description string +		prefix      string +		want        string +	}{ +		{ +			description: "no prefix", +			want:        "/sigsum/v0/add-leaf", +		}, +		{ +			description: "a prefix", +			prefix:      "test-prefix", +			want:        "/test-prefix/sigsum/v0/add-leaf", +		}, +	} { +		instance := &Instance{ +			Config: Config{ +				Prefix: table.prefix, +			}, +		} +		handler := Handler{ +			Instance: instance, +			Handler:  addLeaf, +			Endpoint: types.EndpointAddLeaf, +			Method:   http.MethodPost, +		} +		if got, want := handler.Path(), table.want; got != want { +			t.Errorf("got path %v but wanted %v", got, want)  		}  	} -	t.Fatalf("must handle endpoint: %v", e) -	return Handler{}  }  func TestAddLeaf(t *testing.T) { @@ -77,29 +154,29 @@ func TestAddLeaf(t *testing.T) {  		},  		{  			description: "invalid: bad request (signature error)", -			ascii:       mustLeafBuffer(t, 10, &[types.HashSize]byte{}, false), +			ascii:       mustLeafBuffer(t, 10, types.Hash{}, false),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: bad request (shard hint is before shard start)", -			ascii:       mustLeafBuffer(t, 9, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, 9, types.Hash{}, true),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: bad request (shard hint is after shard end)", -			ascii:       mustLeafBuffer(t, uint64(time.Now().Unix())+1024, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, uint64(time.Now().Unix())+1024, types.Hash{}, true),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: failed verifying domain hint", -			ascii:       mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:   true,  			errDNS:      fmt.Errorf("something went wrong"),  			wantCode:    http.StatusBadRequest,  		},  		{  			description:    "invalid: backend failure", -			ascii:          mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:          mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:      true,  			expectTrillian: true,  			errTrillian:    fmt.Errorf("something went wrong"), @@ -107,7 +184,7 @@ func TestAddLeaf(t *testing.T) {  		},  		{  			description:    "valid", -			ascii:          mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:          mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:      true,  			expectTrillian: true,  			wantCode:       http.StatusOK, @@ -117,11 +194,11 @@ func TestAddLeaf(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			dns := mocks.NewMockVerifier(ctrl) +			dns := mocksDNS.NewMockVerifier(ctrl)  			if table.expectDNS {  				dns.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.errDNS)  			} -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expectTrillian {  				client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.errTrillian)  			} @@ -150,10 +227,9 @@ func TestAddLeaf(t *testing.T) {  func TestAddCosignature(t *testing.T) {  	buf := func() io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%x%s"+"%s%s%x%s", -			types.Cosignature, types.Delim, make([]byte, types.SignatureSize), types.EOL, -			types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", +			"cosignature", types.Signature{}, +			"key_hash", *types.HashFn(testWitVK[:]),  		))  	}  	for _, table := range []struct { @@ -170,10 +246,9 @@ func TestAddCosignature(t *testing.T) {  		},  		{  			description: "invalid: bad request (unknown witness)", -			ascii: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%x%s"+"%s%s%x%s", -				types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, -				types.KeyHash, types.Delim, *types.Hash(testWitVK[1:]), types.EOL, +			ascii: bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", +				"cosignature", types.Signature{}, +				"key_hash", *types.HashFn(testWitVK[1:]),  			)),  			wantCode: http.StatusBadRequest,  		}, @@ -195,7 +270,7 @@ func TestAddCosignature(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().AddCosignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.err)  			} @@ -246,7 +321,7 @@ func TestGetTreeHeadLatest(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().Latest(gomock.Any()).Return(table.rsp, table.err)  			} @@ -297,7 +372,7 @@ func TestGetTreeToSign(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().ToSign(gomock.Any()).Return(table.rsp, table.err)  			} @@ -348,7 +423,7 @@ func TestGetTreeCosigned(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().Cosigned(gomock.Any()).Return(table.rsp, table.err)  			} @@ -376,10 +451,9 @@ func TestGetTreeCosigned(t *testing.T) {  func TestGetConsistencyProof(t *testing.T) {  	buf := func(oldSize, newSize int) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%d%s"+"%s%s%d%s", -			types.OldSize, types.Delim, oldSize, types.EOL, -			types.NewSize, types.Delim, newSize, types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", +			"old_size", oldSize, +			"new_size", newSize,  		))  	}  	for _, table := range []struct { @@ -419,8 +493,8 @@ func TestGetConsistencyProof(t *testing.T) {  			rsp: &types.ConsistencyProof{  				OldSize: 1,  				NewSize: 2, -				Path: []*[types.HashSize]byte{ -					types.Hash(nil), +				Path: []types.Hash{ +					*types.HashFn([]byte{}),  				},  			},  			wantCode: http.StatusOK, @@ -430,7 +504,7 @@ func TestGetConsistencyProof(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -457,11 +531,10 @@ func TestGetConsistencyProof(t *testing.T) {  }  func TestGetInclusionProof(t *testing.T) { -	buf := func(hash *[types.HashSize]byte, treeSize int) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%x%s"+"%s%s%d%s", -			types.LeafHash, types.Delim, hash[:], types.EOL, -			types.TreeSize, types.Delim, treeSize, types.EOL, +	buf := func(hash *types.Hash, treeSize int) io.Reader { +		return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%d\n", +			"leaf_hash", hash[:], +			"tree_size", treeSize,  		))  	}  	for _, table := range []struct { @@ -479,25 +552,25 @@ func TestGetInclusionProof(t *testing.T) {  		},  		{  			description: "invalid: bad request (no proof for tree size)", -			ascii:       buf(types.Hash(nil), 1), +			ascii:       buf(types.HashFn([]byte{}), 1),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: backend failure", -			ascii:       buf(types.Hash(nil), 2), +			ascii:       buf(types.HashFn([]byte{}), 2),  			expect:      true,  			err:         fmt.Errorf("something went wrong"),  			wantCode:    http.StatusInternalServerError,  		},  		{  			description: "valid", -			ascii:       buf(types.Hash(nil), 2), +			ascii:       buf(types.HashFn([]byte{}), 2),  			expect:      true,  			rsp: &types.InclusionProof{  				TreeSize:  2,  				LeafIndex: 0, -				Path: []*[types.HashSize]byte{ -					types.Hash(nil), +				Path: []types.Hash{ +					*types.HashFn([]byte{}),  				},  			},  			wantCode: http.StatusOK, @@ -507,7 +580,7 @@ func TestGetInclusionProof(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetInclusionProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -535,19 +608,18 @@ func TestGetInclusionProof(t *testing.T) {  func TestGetLeaves(t *testing.T) {  	buf := func(startSize, endSize int64) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%d%s"+"%s%s%d%s", -			types.StartSize, types.Delim, startSize, types.EOL, -			types.EndSize, types.Delim, endSize, types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", +			"start_size", startSize, +			"end_size", endSize,  		))  	}  	for _, table := range []struct {  		description string -		ascii       io.Reader       // buffer used to populate HTTP request -		expect      bool            // set if a mock answer is expected -		rsp         *types.LeafList // list of leaves from Trillian client -		err         error           // error from Trillian client -		wantCode    int             // HTTP status ok +		ascii       io.Reader     // buffer used to populate HTTP request +		expect      bool          // set if a mock answer is expected +		rsp         *types.Leaves // list of leaves from Trillian client +		err         error         // error from Trillian client +		wantCode    int           // HTTP status ok  	}{  		{  			description: "invalid: bad request (parser error)", @@ -570,18 +642,16 @@ func TestGetLeaves(t *testing.T) {  			description: "valid: one more entry than the configured MaxRange",  			ascii:       buf(0, testConfig.MaxRange), // query will be pruned  			expect:      true, -			rsp: func() *types.LeafList { -				var list types.LeafList +			rsp: func() *types.Leaves { +				var list types.Leaves  				for i := int64(0); i < testConfig.MaxRange; i++ { -					list = append(list[:], &types.Leaf{ -						Message: types.Message{ +					list = append(list[:], types.Leaf{ +						Statement: types.Statement{  							ShardHint: 0, -							Checksum:  types.Hash(nil), -						}, -						SigIdent: types.SigIdent{ -							Signature: &[types.SignatureSize]byte{}, -							KeyHash:   types.Hash(nil), +							Checksum:  types.Hash{},  						}, +						Signature: types.Signature{}, +						KeyHash:   types.Hash{},  					})  				}  				return &list @@ -593,7 +663,7 @@ func TestGetLeaves(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -619,43 +689,48 @@ func TestGetLeaves(t *testing.T) {  				return  			} -			// TODO: check that we got the right leaves back.  It is especially -			// important that we check that we got the right number of leaves. -			// -			// Pseuducode for when we have types.LeafList.UnmarshalASCII() -			// -			//list := &types.LeafList{} -			//if err := list.UnmarshalASCII(w.Body); err != nil { -			//	t.Fatalf("must unmarshal leaf list: %v", err) -			//} -			//if got, want := list, table.rsp; !reflect.DeepEqual(got, want) { -			//	t.Errorf("got leaf list\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -			//} +			list := types.Leaves{} +			if err := list.FromASCII(w.Body); err != nil { +				t.Fatalf("must unmarshal leaf list: %v", err) +			} +			if got, want := &list, table.rsp; !reflect.DeepEqual(got, want) { +				t.Errorf("got leaf list\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) +			}  		}()  	}  } -func mustLeafBuffer(t *testing.T, shardHint uint64, checksum *[types.HashSize]byte, wantSig bool) io.Reader { +func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { +	for _, handler := range i.Handlers() { +		if handler.Endpoint == e { +			return handler +		} +	} +	t.Fatalf("must handle endpoint: %v", e) +	return Handler{} +} + +func mustLeafBuffer(t *testing.T, shardHint uint64, checksum types.Hash, wantSig bool) io.Reader {  	t.Helper()  	vk, sk, err := ed25519.GenerateKey(rand.Reader)  	if err != nil {  		t.Fatalf("must generate ed25519 keys: %v", err)  	} -	msg := types.Message{ +	msg := types.Statement{  		ShardHint: shardHint,  		Checksum:  checksum,  	} -	sig := ed25519.Sign(sk, msg.Marshal()) +	sig := ed25519.Sign(sk, msg.ToBinary())  	if !wantSig {  		sig[0] += 1  	}  	return bytes.NewBufferString(fmt.Sprintf( -		"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", -		types.ShardHint, types.Delim, shardHint, types.EOL, -		types.Checksum, types.Delim, checksum[:], types.EOL, -		types.Signature, types.Delim, sig, types.EOL, -		types.VerificationKey, types.Delim, vk, types.EOL, -		types.DomainHint, types.Delim, "example.com", types.EOL, +		"%s=%d\n"+"%s=%x\n"+"%s=%x\n"+"%s=%x\n"+"%s=%s\n", +		"shard_hint", shardHint, +		"checksum", checksum[:], +		"signature", sig, +		"verification_key", vk, +		"domain_hint", "example.com",  	))  } diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 4dff31a..9f71aa3 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -3,16 +3,15 @@ package instance  import (  	"context"  	"crypto" -	"crypto/ed25519"  	"fmt"  	"net/http"  	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db"  	"git.sigsum.org/sigsum-log-go/pkg/dns"  	"git.sigsum.org/sigsum-log-go/pkg/state" -	"git.sigsum.org/sigsum-log-go/pkg/trillian" -	"git.sigsum.org/sigsum-log-go/pkg/types" -	"github.com/golang/glog"  )  // Config is a collection of log parameters @@ -26,27 +25,18 @@ type Config struct {  	ShardStart uint64        // Shard interval start (num seconds since UNIX epoch)  	// Witnesses map trusted witness identifiers to public verification keys -	Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte +	Witnesses map[types.Hash]types.PublicKey  }  // Instance is an instance of the log's front-end  type Instance struct {  	Config                      // configuration parameters -	Client   trillian.Client    // provides access to the Trillian backend +	Client   db.Client          // provides access to the Trillian backend  	Signer   crypto.Signer      // provides access to Ed25519 private key  	Stateman state.StateManager // coordinates access to (co)signed tree heads  	DNS      dns.Verifier       // checks if domain name knows a public key  } -// Handler implements the http.Handler interface, and contains a reference -// to a sigsum server instance as well as a function that uses it. -type Handler struct { -	Instance *Instance -	Endpoint types.Endpoint -	Method   string -	Handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) -} -  // Handlers returns a list of sigsum handlers  func (i *Instance) Handlers() []Handler {  	return []Handler{ @@ -62,51 +52,13 @@ func (i *Instance) Handlers() []Handler {  	}  } -// Path returns a path that should be configured for this handler -func (h Handler) Path() string { -	if len(h.Instance.Prefix) == 0 { -		return h.Endpoint.Path("", "sigsum", "v0") -	} -	return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0") -} - -// ServeHTTP is part of the http.Handler interface -func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { -	// export prometheus metrics -	var now time.Time = time.Now() -	var statusCode int -	defer func() { -		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) -		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) -	}() -	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) - -	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) -	defer cancel() - -	if r.Method != a.Method { -		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) -		http.Error(w, "", http.StatusMethodNotAllowed) -		return -	} - -	statusCode, err := a.Handler(ctx, a.Instance, w, r) -	if err != nil { -		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) -		http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) -	} -} - -func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*types.LeafRequest, error) { -	var req types.LeafRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*requests.Leaf, error) { +	var req requests.Leaf +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	} -	vk := ed25519.PublicKey(req.VerificationKey[:]) -	msg := req.Message.Marshal() -	sig := req.Signature[:] -	if !ed25519.Verify(vk, msg, sig) { +	if !req.Statement.Verify(&req.VerificationKey, &req.Signature) {  		return nil, fmt.Errorf("invalid signature")  	}  	shardEnd := uint64(time.Now().Unix()) @@ -116,27 +68,27 @@ func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*t  	if req.ShardHint > shardEnd {  		return nil, fmt.Errorf("invalid shard hint: %d not in [%d, %d]", req.ShardHint, i.ShardStart, shardEnd)  	} -	if err := i.DNS.Verify(ctx, req.DomainHint, req.VerificationKey); err != nil { +	if err := i.DNS.Verify(ctx, req.DomainHint, &req.VerificationKey); err != nil {  		return nil, fmt.Errorf("invalid domain hint: %v", err)  	}  	return &req, nil  } -func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { -	var req types.CosignatureRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*requests.Cosignature, error) { +	var req requests.Cosignature +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	} -	if _, ok := i.Witnesses[*req.KeyHash]; !ok { +	if _, ok := i.Witnesses[req.KeyHash]; !ok {  		return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash)  	}  	return &req, nil  } -func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.ConsistencyProofRequest, error) { -	var req types.ConsistencyProofRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*requests.ConsistencyProof, error) { +	var req requests.ConsistencyProof +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.OldSize < 1 {  		return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) @@ -147,10 +99,10 @@ func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.Cons  	return &req, nil  } -func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.InclusionProofRequest, error) { -	var req types.InclusionProofRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*requests.InclusionProof, error) { +	var req requests.InclusionProof +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.TreeSize < 2 {  		// TreeSize:0 => not possible to prove inclusion of anything @@ -160,10 +112,10 @@ func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.Inclus  	return &req, nil  } -func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { -	var req types.LeavesRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*requests.Leaves, error) { +	var req requests.Leaves +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.StartSize > req.EndSize { diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go deleted file mode 100644 index 6ca7baf..0000000 --- a/pkg/instance/instance_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package instance - -import ( -	"net/http" -	"net/http/httptest" -	"testing" - -	"git.sigsum.org/sigsum-log-go/pkg/types" -) - -// TestHandlers check that the expected handlers are configured -func TestHandlers(t *testing.T) { -	endpoints := map[types.Endpoint]bool{ -		types.EndpointAddLeaf:             false, -		types.EndpointAddCosignature:      false, -		types.EndpointGetTreeHeadLatest:   false, -		types.EndpointGetTreeHeadToSign:   false, -		types.EndpointGetTreeHeadCosigned: false, -		types.EndpointGetConsistencyProof: false, -		types.EndpointGetInclusionProof:   false, -		types.EndpointGetLeaves:           false, -		types.Endpoint("get-checkpoint"):  false, -	} -	i := &Instance{ -		Config: testConfig, -	} -	for _, handler := range i.Handlers() { -		if _, ok := endpoints[handler.Endpoint]; !ok { -			t.Errorf("got unexpected endpoint: %s", handler.Endpoint) -		} -		endpoints[handler.Endpoint] = true -	} -	for endpoint, ok := range endpoints { -		if !ok { -			t.Errorf("endpoint %s is not configured", endpoint) -		} -	} -} - -// TestServeHTTP checks that invalid HTTP methods are rejected -func TestServeHTTP(t *testing.T) { -	i := &Instance{ -		Config: testConfig, -	} -	for _, handler := range i.Handlers() { -		// Prepare invalid HTTP request -		method := http.MethodPost -		if method == handler.Method { -			method = http.MethodGet -		} -		url := handler.Endpoint.Path("http://example.com", i.Prefix) -		req, err := http.NewRequest(method, url, nil) -		if err != nil { -			t.Fatalf("must create HTTP request: %v", err) -		} -		w := httptest.NewRecorder() - -		// Check that it is rejected -		handler.ServeHTTP(w, req) -		if got, want := w.Code, http.StatusMethodNotAllowed; got != want { -			t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) -		} -	} -} - -// TestPath checks that Path works for an endpoint (add-leaf) -func TestPath(t *testing.T) { -	for _, table := range []struct { -		description string -		prefix      string -		want        string -	}{ -		{ -			description: "no prefix", -			want:        "/sigsum/v0/add-leaf", -		}, -		{ -			description: "a prefix", -			prefix:      "test-prefix", -			want:        "/test-prefix/sigsum/v0/add-leaf", -		}, -	} { -		instance := &Instance{ -			Config: Config{ -				Prefix: table.prefix, -			}, -		} -		handler := Handler{ -			Instance: instance, -			Handler:  addLeaf, -			Endpoint: types.EndpointAddLeaf, -			Method:   http.MethodPost, -		} -		if got, want := handler.Path(), table.want; got != want { -			t.Errorf("got path %v but wanted %v", got, want) -		} -	} -} | 
