aboutsummaryrefslogtreecommitdiff
path: root/handler_test.go
diff options
context:
space:
mode:
authorRasmus Dahlberg <rasmus.dahlberg@kau.se>2021-02-25 14:36:35 +0100
committerRasmus Dahlberg <rasmus.dahlberg@kau.se>2021-02-25 14:36:35 +0100
commitc05c22ddbc771e7713849cae40f9d91bfafa0503 (patch)
treeb97d11ab2a914806e6f671f9aff1cab9767b2eab /handler_test.go
parentc9b4b43654f0ff26207cc63449f13298cd3c56e8 (diff)
major refactor based on README.md and TODOs
Updated types, improved units tests, isolated most test data to have it in one place, renamed and created new files to improve readability, and fixed a bunch of minor TODOs.
Diffstat (limited to 'handler_test.go')
-rw-r--r--handler_test.go925
1 files changed, 0 insertions, 925 deletions
diff --git a/handler_test.go b/handler_test.go
deleted file mode 100644
index 804ef67..0000000
--- a/handler_test.go
+++ /dev/null
@@ -1,925 +0,0 @@
-package stfe
-
-// TODO: refactor tests
-
-import (
- "bytes"
- "context"
- "crypto"
- "fmt"
- "testing"
-
- "crypto/ed25519"
- "encoding/base64"
- "encoding/json"
- "net/http"
- "net/http/httptest"
-
- "github.com/golang/mock/gomock"
- "github.com/google/certificate-transparency-go/trillian/mockclient"
- cttestdata "github.com/google/certificate-transparency-go/trillian/testdata"
- "github.com/google/trillian"
-
- "github.com/system-transparency/stfe/namespace/testdata"
-)
-
-type testHandler struct {
- mockCtrl *gomock.Controller
- client *mockclient.MockTrillianLogClient
- instance *Instance
-}
-
-func newTestHandler(t *testing.T, signer crypto.Signer, sth *StItem) *testHandler {
- ctrl := gomock.NewController(t)
- client := mockclient.NewMockTrillianLogClient(ctrl)
- lp := makeTestLogParameters(t, signer)
- source := &ActiveSthSource{
- client: client,
- logParameters: lp,
- }
- if sth != nil {
- source.currCosth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{
- SignatureV1{
- Namespace: *mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk),
- Signature: testSignature,
- },
- })
- source.nextCosth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil)
- source.cosignatureFrom = make(map[string]bool)
- }
- return &testHandler{
- mockCtrl: ctrl,
- client: client,
- instance: &Instance{
- Client: client,
- LogParameters: lp,
- SthSource: source,
- },
- }
-}
-
-func (th *testHandler) getHandlers(t *testing.T) map[Endpoint]Handler {
- return map[Endpoint]Handler{
- EndpointGetLatestSth: Handler{instance: th.instance, handler: getSth, endpoint: EndpointGetLatestSth, method: http.MethodGet},
- EndpointGetConsistencyProof: Handler{instance: th.instance, handler: getConsistencyProof, endpoint: EndpointGetConsistencyProof, method: http.MethodGet},
- EndpointGetProofByHash: Handler{instance: th.instance, handler: getProofByHash, endpoint: EndpointGetProofByHash, method: http.MethodGet},
- EndpointGetAnchors: Handler{instance: th.instance, handler: getAnchors, endpoint: EndpointGetAnchors, method: http.MethodGet},
- EndpointGetEntries: Handler{instance: th.instance, handler: getEntries, endpoint: EndpointGetEntries, method: http.MethodGet},
- EndpointGetStableSth: Handler{instance: th.instance, handler: getStableSth, endpoint: EndpointGetStableSth, method: http.MethodGet},
- EndpointGetCosignedSth: Handler{instance: th.instance, handler: getCosi, endpoint: EndpointGetCosignedSth, method: http.MethodGet},
- }
-}
-
-func (th *testHandler) getHandler(t *testing.T, endpoint Endpoint) Handler {
- handler, ok := th.getHandlers(t)[endpoint]
- if !ok {
- t.Fatalf("no such get endpoint: %s", endpoint)
- }
- return handler
-}
-
-func (th *testHandler) postHandlers(t *testing.T) map[Endpoint]Handler {
- return map[Endpoint]Handler{
- EndpointAddEntry: Handler{instance: th.instance, handler: addEntry, endpoint: EndpointAddEntry, method: http.MethodPost},
- EndpointAddCosignature: Handler{instance: th.instance, handler: addCosi, endpoint: EndpointAddCosignature, method: http.MethodPost},
- }
-}
-
-func (th *testHandler) postHandler(t *testing.T, endpoint Endpoint) Handler {
- handler, ok := th.postHandlers(t)[endpoint]
- if !ok {
- t.Fatalf("no such post endpoint: %s", endpoint)
- }
- return handler
-}
-
-// TestGetHandlersRejectPost checks that all get handlers reject post requests
-func TestGetHandlersRejectPost(t *testing.T) {
- th := newTestHandler(t, nil, nil)
- defer th.mockCtrl.Finish()
-
- for endpoint, handler := range th.getHandlers(t) {
- t.Run(string(endpoint), func(t *testing.T) {
- s := httptest.NewServer(handler)
- defer s.Close()
-
- url := endpoint.Path(s.URL, th.instance.LogParameters.Prefix)
- if rsp, err := http.Post(url, "application/json", nil); err != nil {
- t.Fatalf("http.Post(%s)=(_,%q), want (_,nil)", url, err)
- } else if rsp.StatusCode != http.StatusMethodNotAllowed {
- t.Errorf("http.Post(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed)
- }
- })
- }
-}
-
-// TestPostHandlersRejectGet checks that all post handlers reject get requests
-func TestPostHandlersRejectGet(t *testing.T) {
- th := newTestHandler(t, nil, nil)
- defer th.mockCtrl.Finish()
-
- for endpoint, handler := range th.postHandlers(t) {
- t.Run(string(endpoint), func(t *testing.T) {
- s := httptest.NewServer(handler)
- defer s.Close()
-
- url := endpoint.Path(s.URL, th.instance.LogParameters.Prefix)
- if rsp, err := http.Get(url); err != nil {
- t.Fatalf("http.Get(%s)=(_,%q), want (_,nil)", url, err)
- } else if rsp.StatusCode != http.StatusMethodNotAllowed {
- t.Errorf("http.Get(%s)=(%d,nil), want (%d, nil)", url, rsp.StatusCode, http.StatusMethodNotAllowed)
- }
- })
- }
-}
-
-//// TestGetAnchors checks for a valid number of decodable trust anchors
-//func TestGetAnchors(t *testing.T) {
-// // TODO: refactor with namespaces
-// //th := newTestHandler(t, nil)
-// //defer th.mockCtrl.Finish()
-//
-// //url := EndpointGetAnchors.Path("http://example.com", th.instance.LogParameters.Prefix)
-// //req, err := http.NewRequest("GET", url, nil)
-// //if err != nil {
-// // t.Fatalf("failed creating http request: %v", err)
-// //}
-//
-// //w := httptest.NewRecorder()
-// //th.getHandler(t, EndpointGetAnchors).ServeHTTP(w, req)
-// //if w.Code != http.StatusOK {
-// // t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, http.StatusOK)
-// // return
-// //}
-//
-// //var derAnchors [][]byte
-// //if err := json.Unmarshal([]byte(w.Body.String()), &derAnchors); err != nil {
-// // t.Errorf("failed unmarshaling trust anchors response: %v", err)
-// // return
-// //}
-// //if got, want := len(derAnchors), len(th.instance.LogParameters.); got != want {
-// // t.Errorf("unexpected trust anchor count %d, want %d", got, want)
-// //}
-// //if _, err := x509util.ParseDerList(derAnchors); err != nil {
-// // t.Errorf("failed decoding trust anchors: %v", err)
-// //}
-//}
-
-func TestGetEntries(t *testing.T) {
- for _, table := range []struct {
- description string
- breq *GetEntriesRequest
- trsp *trillian.GetLeavesByRangeResponse
- terr error
- wantCode int
- wantErrText string
- }{
- {
- description: "bad request parameters",
- breq: &GetEntriesRequest{
- Start: 1,
- End: 0,
- },
- wantCode: http.StatusBadRequest,
- wantErrText: http.StatusText(http.StatusBadRequest) + "\n",
- },
- {
- description: "empty trillian response",
- breq: &GetEntriesRequest{
- Start: 0,
- End: 1,
- },
- terr: fmt.Errorf("back-end failure"),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- },
- // TODO: make invalid get-entries response
- //{
- // description: "invalid get-entries response",
- // breq: &GetEntriesRequest{
- // Start: 0,
- // End: 1,
- // },
- // trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk),
- // wantCode: http.StatusInternalServerError,
- // wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- //},
- {
- description: "valid get-entries response",
- breq: &GetEntriesRequest{
- Start: 0,
- End: 1,
- },
- trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk),
- wantCode: http.StatusOK,
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, nil)
- defer th.mockCtrl.Finish()
-
- url := EndpointGetEntries.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("must create http request: %v", err)
- }
- q := req.URL.Query()
- q.Add("start", fmt.Sprintf("%d", table.breq.Start))
- q.Add("end", fmt.Sprintf("%d", table.breq.End))
- req.URL.RawQuery = q.Encode()
-
- if table.trsp != nil || table.terr != nil {
- th.client.EXPECT().GetLeavesByRange(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr)
- }
- w := httptest.NewRecorder()
- th.getHandler(t, EndpointGetEntries).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
-
- body := w.Body.String()
- if w.Code != http.StatusOK {
- if body != table.wantErrText {
- t.Errorf("GET(%s)=%q, want text %q", url, body, table.wantErrText)
- }
- return
- }
-
- var rsps []*GetEntryResponse
- if err := json.Unmarshal([]byte(body), &rsps); err != nil {
- t.Errorf("failed parsing list of log entries: %v", err)
- return
- }
- for i, rsp := range rsps {
- var item StItem
- if err := item.Unmarshal(rsp.Item); err != nil {
- t.Errorf("failed unmarshaling StItem: %v", err)
- } else {
- if item.Format != StFormatChecksumV1 {
- t.Errorf("invalid StFormat: got %v, want %v", item.Format, StFormatChecksumV1)
- }
- checksum := item.ChecksumV1
- if got, want := checksum.Package, []byte(fmt.Sprintf("%s_%d", testPackage, int64(i)+table.breq.Start)); !bytes.Equal(got, want) {
- t.Errorf("got package name %s, want %s", string(got), string(want))
- }
- if got, want := checksum.Checksum, make([]byte, 32); !bytes.Equal(got, want) {
- t.Errorf("got package checksum %X, want %X", got, want)
- }
- // TODO: check namespace?
- }
-
- // TODO: verify signaturew w/ namespace?
- //if !ed25519.Verify(chain[0].PublicKey.(ed25519.PublicKey), rsp.Item, rsp.Signature) {
- // t.Errorf("invalid ed25519 signature")
- //}
- }
- }()
- }
-}
-
-func TestAddEntry(t *testing.T) {
- for _, table := range []struct {
- description string
- breq *bytes.Buffer
- trsp *trillian.QueueLeafResponse
- terr error
- wantCode int
- wantErrText string
- signer crypto.Signer
- }{
- {
- description: "empty trillian response",
- breq: mustMakeEd25519ChecksumV1Buffer(t, testPackage, testChecksum, testdata.Ed25519Vk, testdata.Ed25519Sk),
- terr: fmt.Errorf("back-end failure"),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- },
- {
- description: "bad request parameters: invalid signature",
- breq: mustMakeEd25519ChecksumV1Buffer(t, testPackage, testChecksum, make([]byte, 32), testdata.Ed25519Sk),
- wantCode: http.StatusBadRequest,
- wantErrText: http.StatusText(http.StatusBadRequest) + "\n",
- },
- {
- description: "log signature failure",
- breq: mustMakeEd25519ChecksumV1Buffer(t, testPackage, testChecksum, testdata.Ed25519Vk, testdata.Ed25519Sk),
- trsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk, false),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signing failed")),
- },
- {
- description: "valid add-entry request-response",
- breq: mustMakeEd25519ChecksumV1Buffer(t, testPackage, testChecksum, testdata.Ed25519Vk, testdata.Ed25519Sk),
- trsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk, false),
- wantCode: http.StatusOK,
- signer: cttestdata.NewSignerWithFixedSig(nil, make([]byte, 32)),
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, table.signer, nil)
- defer th.mockCtrl.Finish()
-
- url := EndpointAddEntry.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("POST", url, table.breq)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- req.Header.Set("Content-Type", "application/json")
-
- if table.trsp != nil || table.terr != nil {
- th.client.EXPECT().QueueLeaf(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr)
- }
- w := httptest.NewRecorder()
- th.postHandler(t, EndpointAddEntry).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
-
- body := w.Body.String()
- if w.Code != http.StatusOK {
- if body != table.wantErrText {
- t.Errorf("GET(%s)=%q, want text %q", url, body, table.wantErrText)
- }
- return
- }
-
- // status code is http.StatusOK, check response
- var data []byte
- if err := json.Unmarshal([]byte(body), &data); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- var item StItem
- if err := item.Unmarshal(data); err != nil {
- t.Errorf("failed unmarshaling StItem: %v, wanted ok", err)
- return
- }
- if item.Format != StFormatSignedDebugInfoV1 {
- t.Errorf("invalid StFormat: got %v, want %v", item.Format, StFormatSignedDebugInfoV1)
- }
- sdi := item.SignedDebugInfoV1
- if !bytes.Equal(sdi.LogId, th.instance.LogParameters.LogId) {
- t.Errorf("want log id %X, got %X", sdi.LogId, th.instance.LogParameters.LogId)
- }
- if len(sdi.Message) == 0 {
- t.Errorf("expected message, got none")
- }
- if !bytes.Equal(sdi.Signature, make([]byte, 32)) {
- t.Errorf("want signature %X, got %X", sdi.Signature, make([]byte, 32))
- }
- }()
- }
-}
-
-func TestGetSth(t *testing.T) {
- tr := makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32))
- tr.SignedLogRoot.LogRoot = tr.SignedLogRoot.LogRoot[1:]
- for _, table := range []struct {
- description string
- trsp *trillian.GetLatestSignedLogRootResponse
- terr error
- wantCode int
- wantErrText string
- signer crypto.Signer
- }{
- {
- description: "empty trillian response",
- terr: fmt.Errorf("back-end failure"),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- },
- {
- description: "marshal failure: no signature",
- trsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- signer: cttestdata.NewSignerWithFixedSig(nil, make([]byte, 0)),
- },
- {
- description: "signature failure",
- trsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signing failed")),
- },
- {
- description: "valid request and response",
- trsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)),
- wantCode: http.StatusOK,
- signer: cttestdata.NewSignerWithFixedSig(nil, make([]byte, 32)),
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, table.signer, nil)
- defer th.mockCtrl.Finish()
-
- url := EndpointGetLatestSth.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
-
- w := httptest.NewRecorder()
- th.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr)
- th.getHandler(t, EndpointGetLatestSth).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
-
- body := w.Body.String()
- if w.Code != http.StatusOK {
- if body != table.wantErrText {
- t.Errorf("GET(%s)=%q, want text %q", url, body, table.wantErrText)
- }
- return
- }
-
- // status code is http.StatusOK, check response
- var data []byte
- if err := json.Unmarshal([]byte(body), &data); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- var item StItem
- if err := item.Unmarshal(data); err != nil {
- t.Errorf("failed unmarshaling StItem: %v, wanted ok", err)
- return
- }
- if item.Format != StFormatSignedTreeHeadV1 {
- t.Errorf("invalid StFormat: got %v, want %v", item.Format, StFormatSignedTreeHeadV1)
- }
- sth := item.SignedTreeHeadV1
- if !bytes.Equal(sth.LogId, th.instance.LogParameters.LogId) {
- t.Errorf("want log id %X, got %X", sth.LogId, th.instance.LogParameters.LogId)
- }
- if !bytes.Equal(sth.Signature, make([]byte, 32)) {
- t.Errorf("want signature %X, got %X", sth.Signature, make([]byte, 32))
- }
- if sth.TreeHead.TreeSize != 0 {
- t.Errorf("want tree size %d, got %d", 0, sth.TreeHead.TreeSize)
- }
- if sth.TreeHead.Timestamp != 0 {
- t.Errorf("want timestamp %d, got %d", 0, sth.TreeHead.Timestamp)
- }
- if !bytes.Equal(sth.TreeHead.RootHash.Data, make([]byte, 32)) {
- t.Errorf("want root hash %X, got %X", make([]byte, 32), sth.TreeHead.RootHash)
- }
- if len(sth.TreeHead.Extension) != 0 {
- t.Errorf("want no extensions, got %v", sth.TreeHead.Extension)
- }
- }()
- }
-}
-
-func TestGetConsistencyProof(t *testing.T) {
- fixedProof := [][]byte{
- make([]byte, 32),
- make([]byte, 32),
- }
- for _, table := range []struct {
- description string
- breq *GetConsistencyProofRequest
- trsp *trillian.GetConsistencyProofResponse
- terr error
- wantCode int
- wantErrText string
- }{
- {
- description: "bad request parameters",
- breq: &GetConsistencyProofRequest{
- First: 2,
- Second: 1,
- },
- wantCode: http.StatusBadRequest,
- wantErrText: http.StatusText(http.StatusBadRequest) + "\n",
- },
- {
- description: "empty trillian response",
- breq: &GetConsistencyProofRequest{
- First: 1,
- Second: 2,
- },
- terr: fmt.Errorf("back-end failure"),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- },
- {
- description: "valid request and response",
- breq: &GetConsistencyProofRequest{
- First: 1,
- Second: 2,
- },
- trsp: makeTrillianGetConsistencyProofResponse(t, fixedProof),
- wantCode: http.StatusOK,
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, nil)
- defer th.mockCtrl.Finish()
-
- url := EndpointGetConsistencyProof.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- q := req.URL.Query()
- q.Add("first", fmt.Sprintf("%d", table.breq.First))
- q.Add("second", fmt.Sprintf("%d", table.breq.Second))
- req.URL.RawQuery = q.Encode()
-
- w := httptest.NewRecorder()
- if table.trsp != nil || table.terr != nil {
- th.client.EXPECT().GetConsistencyProof(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr)
- }
- th.getHandler(t, EndpointGetConsistencyProof).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
- body := w.Body.String()
- if w.Code != http.StatusOK {
- if body != table.wantErrText {
- t.Errorf("GET(%s)=%q, want text %q", url, body, table.wantErrText)
- }
- return
- }
-
- // status code is http.StatusOK, check response
- var data []byte
- if err := json.Unmarshal([]byte(body), &data); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- var item StItem
- if err := item.Unmarshal(data); err != nil {
- t.Errorf("failed unmarshaling StItem: %v, wanted ok", err)
- return
- }
- if item.Format != StFormatConsistencyProofV1 {
- t.Errorf("invalid StFormat: got %v, want %v", item.Format, StFormatInclusionProofV1)
- }
- proof := item.ConsistencyProofV1
- if !bytes.Equal(proof.LogId, th.instance.LogParameters.LogId) {
- t.Errorf("want log id %X, got %X", proof.LogId, th.instance.LogParameters.LogId)
- }
- if got, want := proof.TreeSize1, uint64(table.breq.First); got != want {
- t.Errorf("want tree size %d, got %d", want, got)
- }
- if got, want := proof.TreeSize2, uint64(table.breq.Second); got != want {
- t.Errorf("want tree size %d, got %d", want, got)
- }
- if got, want := len(proof.ConsistencyPath), len(fixedProof); got != want {
- t.Errorf("want proof length %d, got %d", want, got)
- return
- }
- for i, nh := range proof.ConsistencyPath {
- if !bytes.Equal(nh.Data, fixedProof[i]) {
- t.Errorf("want proof[%d]=%X, got %X", i, fixedProof[i], nh.Data)
- }
- }
- }()
- }
-}
-
-func TestGetProofByHash(t *testing.T) {
- fixedProof := [][]byte{
- make([]byte, 32),
- make([]byte, 32),
- }
- for _, table := range []struct {
- description string
- breq *GetProofByHashRequest
- trsp *trillian.GetInclusionProofByHashResponse
- terr error
- wantCode int
- wantErrText string
- }{
- {
- description: "bad request parameters",
- breq: &GetProofByHashRequest{
- Hash: make([]byte, 32),
- TreeSize: 0,
- },
- wantCode: http.StatusBadRequest,
- wantErrText: http.StatusText(http.StatusBadRequest) + "\n",
- },
- {
- description: "empty trillian response",
- breq: &GetProofByHashRequest{
- Hash: make([]byte, 32),
- TreeSize: 128,
- },
- terr: fmt.Errorf("back-end failure"),
- wantCode: http.StatusInternalServerError,
- wantErrText: http.StatusText(http.StatusInternalServerError) + "\n",
- },
- {
- description: "valid request and response",
- breq: &GetProofByHashRequest{
- Hash: make([]byte, 32),
- TreeSize: 128,
- },
- trsp: makeTrillianGetInclusionProofByHashResponse(t, 0, fixedProof),
- wantCode: http.StatusOK,
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, nil)
- defer th.mockCtrl.Finish()
-
- url := EndpointGetProofByHash.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- q := req.URL.Query()
- q.Add("hash", base64.StdEncoding.EncodeToString(table.breq.Hash))
- q.Add("tree_size", fmt.Sprintf("%d", table.breq.TreeSize))
- req.URL.RawQuery = q.Encode()
-
- w := httptest.NewRecorder()
- if table.trsp != nil || table.terr != nil {
- th.client.EXPECT().GetInclusionProofByHash(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr)
- }
- th.getHandler(t, EndpointGetProofByHash).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
- body := w.Body.String()
- if w.Code != http.StatusOK {
- if body != table.wantErrText {
- t.Errorf("GET(%s)=%q, want text %q", url, body, table.wantErrText)
- }
- return
- }
-
- // status code is http.StatusOK, check response
- var data []byte
- if err := json.Unmarshal([]byte(body), &data); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- var item StItem
- if err := item.Unmarshal(data); err != nil {
- t.Errorf("failed unmarshaling StItem: %v, wanted ok", err)
- return
- }
- if item.Format != StFormatInclusionProofV1 {
- t.Errorf("invalid StFormat: got %v, want %v", item.Format, StFormatInclusionProofV1)
- }
- proof := item.InclusionProofV1
- if !bytes.Equal(proof.LogId, th.instance.LogParameters.LogId) {
- t.Errorf("want log id %X, got %X", proof.LogId, th.instance.LogParameters.LogId)
- }
- if proof.TreeSize != uint64(table.breq.TreeSize) {
- t.Errorf("want tree size %d, got %d", table.breq.TreeSize, proof.TreeSize)
- }
- if proof.LeafIndex != 0 {
- t.Errorf("want index %d, got %d", 0, proof.LeafIndex)
- }
- if got, want := len(proof.InclusionPath), len(fixedProof); got != want {
- t.Errorf("want proof length %d, got %d", want, got)
- return
- }
- for i, nh := range proof.InclusionPath {
- if !bytes.Equal(nh.Data, fixedProof[i]) {
- t.Errorf("want proof[%d]=%X, got %X", i, fixedProof[i], nh.Data)
- }
- }
- }()
- }
-}
-
-func TestGetStableSth(t *testing.T) {
- for _, table := range cosiTestCases(t) {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, table.sth)
- defer th.mockCtrl.Finish()
-
- // Setup and run client query
- url := EndpointGetStableSth.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- w := httptest.NewRecorder()
- th.getHandler(t, EndpointGetStableSth).ServeHTTP(w, req)
-
- // Check response code
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
- if w.Code != http.StatusOK {
- return
- }
- // Check response bytes
- var gotBytes []byte
- if err := json.Unmarshal([]byte(w.Body.String()), &gotBytes); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- wantBytes, _ := table.sth.Marshal()
- if got, want := gotBytes, wantBytes; !bytes.Equal(got, want) {
- t.Errorf("wanted response %X but got %X in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestGetCosi(t *testing.T) {
- for _, table := range cosiTestCases(t) {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, table.sth)
- defer th.mockCtrl.Finish()
-
- // Setup and run client query
- url := EndpointGetCosignedSth.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- w := httptest.NewRecorder()
- th.getHandler(t, EndpointGetCosignedSth).ServeHTTP(w, req)
-
- // Check response code
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
- if w.Code != http.StatusOK {
- return
- }
- // Check response bytes
- var gotBytes []byte
- if err := json.Unmarshal([]byte(w.Body.String()), &gotBytes); err != nil {
- t.Errorf("failed unmarshaling json: %v, wanted ok", err)
- return
- }
- wantCosth := NewCosignedTreeHeadV1(table.sth.SignedTreeHeadV1, []SignatureV1{
- SignatureV1{
- Namespace: *mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk),
- Signature: testSignature,
- },
- })
- wantBytes, _ := wantCosth.Marshal()
- if got, want := gotBytes, wantBytes; !bytes.Equal(got, want) {
- t.Errorf("wanted response %X but got %X in test %q", got, want, table.description)
- }
- }()
- }
-}
-
-func TestAddCosi(t *testing.T) {
- validSth := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature)
- validSth2 := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp+1000000, testTreeSize, testNodeHash)), testLogId, testSignature)
- for _, table := range []struct {
- description string
- sth *StItem
- breq *bytes.Buffer
- wantCode int
- }{
- {
- description: "invalid request: untrusted witness", // more specific tests can be found in TestNewAddCosignatureRequest
- sth: validSth,
- breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk2, testdata.Ed25519Vk2, validSth),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "invalid request: cosigned wrong sth", // more specific tests can be found in TestAddCosignature
- sth: validSth,
- breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk, testdata.Ed25519Vk, validSth2),
- wantCode: http.StatusBadRequest,
- },
- {
- description: "valid",
- sth: validSth,
- breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk, testdata.Ed25519Vk, validSth),
- wantCode: http.StatusOK,
- },
- } {
- func() { // run deferred functions at the end of each iteration
- th := newTestHandler(t, nil, table.sth)
- defer th.mockCtrl.Finish()
-
- // Setup and run client query
- url := EndpointAddCosignature.Path("http://example.com", th.instance.LogParameters.Prefix)
- req, err := http.NewRequest("POST", url, table.breq)
- if err != nil {
- t.Fatalf("failed creating http request: %v", err)
- }
- req.Header.Set("Content-Type", "application/json")
-
- w := httptest.NewRecorder()
- th.postHandler(t, EndpointAddCosignature).ServeHTTP(w, req)
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
-
- // Check response
- if w.Code != table.wantCode {
- t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode)
- }
- }()
- }
-}
-
-type cosiTestCase struct {
- description string
- sth *StItem
- wantCode int
-}
-
-// cosiTestCases returns test cases used by TestGetStableSth and TestGetCosi
-func cosiTestCases(t *testing.T) []cosiTestCase {
- t.Helper()
- return []cosiTestCase{
- {
- description: "no cosigned/stable sth",
- sth: nil,
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "malformed cosigned/stable sth",
- sth: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), []byte("not a log id"), testSignature),
- wantCode: http.StatusInternalServerError,
- },
- {
- description: "valid",
- sth: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature),
- wantCode: http.StatusOK,
- },
- }
-}
-
-// mustMakeEd25519ChecksumV1 creates an ed25519-signed ChecksumV1 leaf
-func mustMakeEd25519ChecksumV1(t *testing.T, id, checksum, vk, sk []byte) ([]byte, []byte) {
- t.Helper()
- leaf, err := NewChecksumV1(id, checksum, mustNewNamespaceEd25519V1(t, vk)).Marshal()
- if err != nil {
- t.Fatalf("must serialize checksum_v1: %v", err)
- }
- return leaf, ed25519.Sign(ed25519.PrivateKey(sk), leaf)
-}
-
-// mustMakeEd25519ChecksumV1Buffer creates an add-entry data buffer with an
-// Ed25519 namespace that can be posted.
-func mustMakeEd25519ChecksumV1Buffer(t *testing.T, identifier, checksum, vk, sk []byte) *bytes.Buffer {
- t.Helper()
- leaf, signature := mustMakeEd25519ChecksumV1(t, identifier, checksum, vk, sk)
- req := AddEntryRequest{
- Item: leaf,
- Signature: signature,
- }
- data, err := json.Marshal(req)
- if err != nil {
- t.Fatalf("must marshal add-entry request: %v", err)
- }
- return bytes.NewBuffer(data)
-}
-
-// mustMakeAddCosiBuffer creates an add-cosi data buffer
-func mustMakeAddCosiBuffer(t *testing.T, sk, vk []byte, sth *StItem) *bytes.Buffer {
- t.Helper()
- msg, err := sth.Marshal()
- if err != nil {
- t.Fatalf("must marshal sth: %v", err)
- }
- costh := NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{
- SignatureV1{
- Namespace: *mustNewNamespaceEd25519V1(t, vk),
- Signature: ed25519.Sign(ed25519.PrivateKey(sk), msg),
- },
- })
- item, err := costh.Marshal()
- if err != nil {
- t.Fatalf("must marshal costh: %v", err)
- }
- data, err := json.Marshal(AddCosignatureRequest{item})
- if err != nil {
- t.Fatalf("must marshal add-cosi request: %v", err)
- }
- return bytes.NewBuffer(data)
-}
-
-// deadlineMatcher implements gomock.Matcher, such that an error is raised if
-// there is no context.Context deadline set
-type deadlineMatcher struct{}
-
-// newDeadlineMatcher returns a new DeadlineMatcher
-func newDeadlineMatcher() gomock.Matcher {
- return &deadlineMatcher{}
-}
-
-// Matches returns true if the passed interface is a context with a deadline
-func (dm *deadlineMatcher) Matches(i interface{}) bool {
- ctx, ok := i.(context.Context)
- if !ok {
- return false
- }
- _, ok = ctx.Deadline()
- return ok
-}
-
-// String is needed to implement gomock.Matcher
-func (dm *deadlineMatcher) String() string {
- return fmt.Sprintf("deadlineMatcher{}")
-}