From c05c22ddbc771e7713849cae40f9d91bfafa0503 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Thu, 25 Feb 2021 14:36:35 +0100 Subject: 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. --- crypto.go | 34 -- crypto_test.go | 125 ------ endpoint.go | 185 +++++++++ endpoint_test.go | 504 ++++++++++++++++++++++++ go.mod | 12 + go.sum | 771 ++++++++++++++++++++++++++++++++++++ handler.go | 245 ------------ handler_test.go | 925 -------------------------------------------- instance.go | 138 ++----- instance_test.go | 320 ++++++--------- log_parameters.go | 71 ++++ log_parameters_test.go | 98 +++++ metric.go | 2 - namespace/namespace.go | 150 ------- namespace/namespace_test.go | 218 ----------- namespace/testdata/data.go | 21 - reqres.go | 228 ----------- reqres_test.go | 365 ----------------- request.go | 103 +++++ request_test.go | 318 +++++++++++++++ server/main.go | 20 +- sth.go | 90 +++-- sth_test.go | 364 ++++++++--------- testdata/data.go | 287 ++++++++++++++ trillian.go | 27 +- trillian_test.go | 302 +++++---------- type.go | 306 --------------- type_test.go | 330 ---------------- types/namespace_test.go | 3 +- types/stitem.go | 71 ++++ types/stitem_test.go | 24 ++ util.go | 40 ++ util_test.go | 17 + 33 files changed, 2986 insertions(+), 3728 deletions(-) delete mode 100644 crypto.go delete mode 100644 crypto_test.go create mode 100644 endpoint.go create mode 100644 endpoint_test.go create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 handler.go delete mode 100644 handler_test.go create mode 100644 log_parameters.go create mode 100644 log_parameters_test.go delete mode 100644 namespace/namespace.go delete mode 100644 namespace/namespace_test.go delete mode 100644 namespace/testdata/data.go delete mode 100644 reqres.go delete mode 100644 reqres_test.go create mode 100644 request.go create mode 100644 request_test.go create mode 100644 testdata/data.go delete mode 100644 type.go delete mode 100644 type_test.go create mode 100644 util.go create mode 100644 util_test.go diff --git a/crypto.go b/crypto.go deleted file mode 100644 index 546fc0a..0000000 --- a/crypto.go +++ /dev/null @@ -1,34 +0,0 @@ -package stfe - -import ( - "fmt" - "time" - - "crypto" - "crypto/rand" -) - -// genV1Sdi issues a new SignedDebugInfoV1 StItem from a serialized leaf value -func (lp *LogParameters) genV1Sdi(serialized []byte) (*StItem, error) { - sig, err := lp.Signer.Sign(rand.Reader, serialized, crypto.Hash(0)) // ed25519 - if err != nil { - return nil, fmt.Errorf("ed25519 signature failed: %v", err) - } - lastSdiTimestamp.Set(float64(time.Now().Unix()), lp.id()) - return NewSignedDebugInfoV1(lp.LogId, []byte("reserved"), sig), nil -} - -// genV1Sth issues a new SignedTreeHeadV1 StItem from a TreeHeadV1 structure -func (lp *LogParameters) genV1Sth(th *TreeHeadV1) (*StItem, error) { - serialized, err := th.Marshal() - if err != nil { - return nil, fmt.Errorf("failed tls marshaling tree head: %v", err) - } - sig, err := lp.Signer.Sign(rand.Reader, serialized, crypto.Hash(0)) // ed25519 - if err != nil { - return nil, fmt.Errorf("ed25519 signature failed: %v", err) - } - lastSthTimestamp.Set(float64(time.Now().Unix()), lp.id()) - lastSthSize.Set(float64(th.TreeSize), lp.id()) - return NewSignedTreeHeadV1(th, lp.LogId, sig), nil -} diff --git a/crypto_test.go b/crypto_test.go deleted file mode 100644 index 75e530e..0000000 --- a/crypto_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package stfe - -import ( - "bytes" - "crypto" - "fmt" - "testing" - - cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" -) - -var ( - testLeaf = make([]byte, 64) -) - -// TestGenV1Sdi tests that a signature failure works as expected, and that -// the issued SDI (if any) is populated correctly. -func TestGenV1Sdi(t *testing.T) { - for _, table := range []struct { - description string - leaf []byte - signer crypto.Signer - wantErr bool - }{ - { - description: "signature failure", - leaf: testLeaf, - signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signer failed")), - wantErr: true, - }, - { - description: "all ok", - leaf: testLeaf, - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), - }, - } { - item, err := makeTestLogParameters(t, table.signer).genV1Sdi(table.leaf) - if err != nil && !table.wantErr { - t.Errorf("signing failed in test %q: %v", table.description, err) - } else if err == nil && table.wantErr { - t.Errorf("signing succeeded but wanted failure in test %q", table.description) - } - if err != nil || table.wantErr { - continue - } - if want, got := item.Format, StFormatSignedDebugInfoV1; got != want { - t.Errorf("got format %s, wanted %s in test %q", got, want, table.description) - continue - } - - sdi := item.SignedDebugInfoV1 - if got, want := sdi.LogId, testLogId; !bytes.Equal(got, want) { - t.Errorf("got logId %X, wanted %X in test %q", got, want, table.description) - } - if got, want := sdi.Message, []byte("reserved"); !bytes.Equal(got, want) { - t.Errorf("got message %s, wanted %s in test %q", got, want, table.description) - } - if got, want := sdi.Signature, testSignature; !bytes.Equal(got, want) { - t.Errorf("got signature %X, wanted %X in test %q", got, want, table.description) - } - } -} - -// TestGenV1Sth tests that a signature failure works as expected, and that -// the issued STH (if any) is populated correctly. -func TestGenV1Sth(t *testing.T) { - th := NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)) - for _, table := range []struct { - description string - th *TreeHeadV1 - signer crypto.Signer - wantErr bool - }{ - { - description: "marshal failure", - th: NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, nil)), - wantErr: true, - }, - { - description: "signature failure", - th: th, - signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signer failed")), - wantErr: true, - }, - { - description: "all ok", - th: th, - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), - }, - } { - item, err := makeTestLogParameters(t, table.signer).genV1Sth(table.th) - if err != nil && !table.wantErr { - t.Errorf("signing failed in test %q: %v", table.description, err) - } else if err == nil && table.wantErr { - t.Errorf("signing succeeded but wanted failure in test %q", table.description) - } - if err != nil || table.wantErr { - continue - } - if want, got := item.Format, StFormatSignedTreeHeadV1; got != want { - t.Errorf("got format %s, wanted %s in test %q", got, want, table.description) - continue - } - - sth := item.SignedTreeHeadV1 - if got, want := sth.LogId, testLogId; !bytes.Equal(got, want) { - t.Errorf("got logId %X, wanted %X in test %q", got, want, table.description) - } - if got, want := sth.Signature, testSignature; !bytes.Equal(got, want) { - t.Errorf("got signature %X, wanted %X in test %q", got, want, table.description) - } - if got, want := sth.TreeHead.Timestamp, th.Timestamp; got != want { - t.Errorf("got timestamp %d, wanted %d in test %q", got, want, table.description) - } - if got, want := sth.TreeHead.TreeSize, th.TreeSize; got != want { - t.Errorf("got tree size %d, wanted %d in test %q", got, want, table.description) - } - if got, want := sth.TreeHead.RootHash.Data, th.RootHash.Data; !bytes.Equal(got, want) { - t.Errorf("got root hash %X, wanted %X in test %q", got, want, table.description) - } - if len(sth.TreeHead.Extension) != 0 { - t.Errorf("got extensions %X, wanted none in test %q", sth.TreeHead.Extension, table.description) - } - } -} diff --git a/endpoint.go b/endpoint.go new file mode 100644 index 0000000..d3da95e --- /dev/null +++ b/endpoint.go @@ -0,0 +1,185 @@ +package stfe + +import ( + "context" + "fmt" + "strings" + + "net/http" + + "github.com/golang/glog" + "github.com/google/trillian" + "github.com/system-transparency/stfe/types" +) + +// Endpoint is a named HTTP API endpoint +type Endpoint string + +const ( + EndpointAddEntry = Endpoint("add-entry") + EndpointAddCosignature = Endpoint("add-cosignature") + EndpointGetLatestSth = Endpoint("get-latest-sth") + EndpointGetStableSth = Endpoint("get-stable-sth") + EndpointGetCosignedSth = Endpoint("get-cosigned-sth") + EndpointGetProofByHash = Endpoint("get-proof-by-hash") + EndpointGetConsistencyProof = Endpoint("get-consistency-proof") + EndpointGetEntries = Endpoint("get-entries") +) + +// Path joins a number of components to form a full endpoint path, e.g., base +// ("example.com"), prefix ("st/v1"), and the endpoint itself ("get-sth"). +func (e Endpoint) Path(components ...string) string { + return strings.Join(append(components, string(e)), "/") +} + +func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling add-entry request") + item, err := i.LogParameters.parseAddEntryV1Request(r) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("parseAddEntryV1Request: %v", err) + } + leaf, err := types.Marshal(*item) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Marshal: %v", err) // should never happen + } + trsp, err := i.Client.QueueLeaf(ctx, &trillian.QueueLeafRequest{ + LogId: i.LogParameters.TreeId, + Leaf: &trillian.LogLeaf{ + LeafValue: leaf, + ExtraData: nil, + }, + }) + if errInner := checkQueueLeaf(trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("bad QueueLeafResponse: %v", errInner) + } + return http.StatusOK, nil +} + +func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling add-cosignature request") + costh, err := i.LogParameters.parseAddCosignatureV1Request(r) + if err != nil { + return http.StatusBadRequest, err + } + if err := i.SthSource.AddCosignature(ctx, costh); err != nil { + return http.StatusBadRequest, err + } + return http.StatusOK, nil +} + +func getLatestSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-latest-sth request") + sth, err := i.SthSource.Latest(ctx) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) + } + if err := writeOctetResponse(w, *sth); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func getStableSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-stable-sth request") + sth, err := i.SthSource.Stable(ctx) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) + } + if err := writeOctetResponse(w, *sth); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func getCosignedSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { + glog.V(3).Info("handling get-cosigned-sth request") + costh, err := i.SthSource.Cosigned(ctx) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("Cosigned: %v", err) + } + if err := writeOctetResponse(w, *costh); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-consistency-proof request") + req, err := i.LogParameters.parseGetConsistencyProofV1Request(r) + if err != nil { + return http.StatusBadRequest, err + } + + trsp, err := i.Client.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ + LogId: i.LogParameters.TreeId, + FirstTreeSize: int64(req.First), + SecondTreeSize: int64(req.Second), + }) + if errInner := checkGetConsistencyProof(i.LogParameters, trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("bad GetConsistencyProofResponse: %v", errInner) + } + + if err := writeOctetResponse(w, *types.NewConsistencyProofV1(i.LogParameters.LogId, req.First, req.Second, NewNodePathFromHashPath(trsp.Proof.Hashes))); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-proof-by-hash request") + req, err := i.LogParameters.parseGetProofByHashV1Request(r) + if err != nil { + return http.StatusBadRequest, err + } + + trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ + LogId: i.LogParameters.TreeId, + LeafHash: req.Hash[:], + TreeSize: int64(req.TreeSize), + OrderBySequence: true, + }) + if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) + } + + if err := writeOctetResponse(w, *types.NewInclusionProofV1(i.LogParameters.LogId, req.TreeSize, uint64(trsp.Proof[0].LeafIndex), NewNodePathFromHashPath(trsp.Proof[0].Hashes))); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { + glog.V(3).Info("handling get-entries request") + req, err := i.LogParameters.parseGetEntriesV1Request(r) + if err != nil { + return http.StatusBadRequest, err + } + + trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ + LogId: i.LogParameters.TreeId, + StartIndex: int64(req.Start), + Count: int64(req.End-req.Start) + 1, + }) + if errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("checkGetLeavesByRangeResponse: %v", errInner) // there is one StatusBadRequest in here tho.. + } + + if rsp, err := NewStItemListFromLeaves(trsp.Leaves); err != nil { + return http.StatusInternalServerError, fmt.Errorf("NewStItemListFromLeaves: %v", err) // should never happen + } else if err := writeOctetResponse(w, *rsp); err != nil { + return http.StatusInternalServerError, fmt.Errorf("writeOctetResponse: %v", err) + } + return http.StatusOK, nil +} + +func writeOctetResponse(w http.ResponseWriter, i interface{}) error { + b, err := types.Marshal(i) + if err != nil { + return fmt.Errorf("Marshal: %v", err) + } + w.Header().Set("Content-Type", "application/octet-stream") + if _, err := w.Write(b); err != nil { + return fmt.Errorf("Write: %v", err) + } + return nil +} diff --git a/endpoint_test.go b/endpoint_test.go new file mode 100644 index 0000000..7772837 --- /dev/null +++ b/endpoint_test.go @@ -0,0 +1,504 @@ +package stfe + +import ( + "bytes" + "fmt" + "reflect" + "testing" + + "net/http" + "net/http/httptest" + + "github.com/golang/mock/gomock" + cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" + "github.com/google/trillian" + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" +) + +func TestEndpointAddEntry(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.QueueLeafResponse + terr error + wantCode int + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + trsp: testdata.DefaultTQlr(t, false), + wantCode: http.StatusOK, + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointAddEntry.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // TODO: deadline matcher? + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointAddEntry).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointAddCosignature(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + wantCode int + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: signed wrong sth", // newLogParameters() use testdata.Ed25519VkLog as default + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog2), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantCode: http.StatusBadRequest, + }, + { + description: "valid", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantCode: http.StatusOK, + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointAddCosignature.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointAddCosignature).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetLatestSth(t *testing.T) { + for _, table := range []struct { + description string + trsp *trillian.GetLatestSignedLogRootResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "backend failure", + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + trsp: testdata.DefaultTSlr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, cttestdata.NewSignerWithFixedSig(nil, testdata.Signature)) + ti.ctrl.Finish() + + // Setup and run client query + url := EndpointGetLatestSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // TODO: deadline matcher? + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetLatestSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetStableSth(t *testing.T) { + for _, table := range []struct { + description string + useBadSource bool + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: sth source failure", + useBadSource: true, + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + wantCode: http.StatusOK, + wantItem: testdata.DefaultSth(t, testdata.Ed25519VkLog), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + ti.ctrl.Finish() + if table.useBadSource { + ti.instance.SthSource = &ActiveSthSource{} + } + + // Setup and run client query + url := EndpointGetStableSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetStableSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetCosignedSth(t *testing.T) { + for _, table := range []struct { + description string + useBadSource bool + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: sth source failure", + useBadSource: true, + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + wantCode: http.StatusOK, + wantItem: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + ti.ctrl.Finish() + if table.useBadSource { + ti.instance.SthSource = &ActiveSthSource{} + } + + // Setup and run client query + url := EndpointGetCosignedSth.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + + w := httptest.NewRecorder() + ti.getHandler(t, EndpointGetCosignedSth).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetProofByHash(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetInclusionProofByHashResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: bytes.NewBuffer(marshal(t, types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash})), + trsp: testdata.DefaultTGipbhr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultInclusionProof(t, 1), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetProofByHash.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // TODO: deadline matcher? + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetProofByHash).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetConsistencyProof(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetConsistencyProofResponse + terr error + wantCode int + wantItem *types.StItem + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", + breq: bytes.NewBuffer(marshal(t, types.GetConsistencyProofV1{First: 1, Second: 2})), + trsp: testdata.DefaultTGcpr(t), + wantCode: http.StatusOK, + wantItem: testdata.DefaultConsistencyProof(t, 1, 2), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetConsistencyProof.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // TODO: deadline matcher? + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetConsistencyProof).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItem + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointGetEntriesV1(t *testing.T) { + for _, table := range []struct { + description string + breq *bytes.Buffer + trsp *trillian.GetLeavesByRangeResponse + terr error + wantCode int + wantItem *types.StItemList + }{ + { + description: "invalid: bad request: empty", + breq: bytes.NewBuffer(nil), + wantCode: http.StatusBadRequest, + }, + { + description: "invalid: bad Trillian response: error", + breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: 0})), + terr: fmt.Errorf("backend failure"), + wantCode: http.StatusInternalServerError, + }, + { + description: "valid", // remember that newLogParameters() have testdata.MaxRange configured + breq: bytes.NewBuffer(marshal(t, types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange - 1)})), + trsp: testdata.DefaultTGlbrr(t, 0, testdata.MaxRange-1), + wantCode: http.StatusOK, + wantItem: testdata.DefaultStItemList(t, 0, uint64(testdata.MaxRange)-1), + }, + } { + func() { // run deferred functions at the end of each iteration + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + url := EndpointGetEntries.Path("http://example.com", ti.instance.LogParameters.Prefix) + req, err := http.NewRequest("POST", url, table.breq) + if err != nil { + t.Fatalf("must create http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + if table.trsp != nil || table.terr != nil { + ti.client.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) // TODO: deadline matcher? + } + + w := httptest.NewRecorder() + ti.postHandler(t, EndpointGetEntries).ServeHTTP(w, req) + if got, want := w.Code, table.wantCode; got != want { + t.Errorf("got error code %d but wanted %d in test %q", got, want, table.description) + } + if w.Code != http.StatusOK { + return + } + + var item types.StItemList + if err := types.Unmarshal([]byte(w.Body.String()), &item); err != nil { + t.Errorf("valid response cannot be unmarshalled in test %q: %v", table.description, err) + } + if got, want := item, *table.wantItem; !reflect.DeepEqual(got, want) { + t.Errorf("got item\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + }() + } +} + +func TestEndpointPath(t *testing.T) { + base, prefix, proto := "http://example.com", "test", "st/v1" + for _, table := range []struct { + endpoint Endpoint + want string + }{ + { + endpoint: EndpointAddEntry, + want: "http://example.com/test/st/v1/add-entry", + }, + { + endpoint: EndpointAddCosignature, + want: "http://example.com/test/st/v1/add-cosignature", + }, + { + endpoint: EndpointGetLatestSth, + want: "http://example.com/test/st/v1/get-latest-sth", + }, + { + endpoint: EndpointGetStableSth, + want: "http://example.com/test/st/v1/get-stable-sth", + }, + { + endpoint: EndpointGetCosignedSth, + want: "http://example.com/test/st/v1/get-cosigned-sth", + }, + { + endpoint: EndpointGetConsistencyProof, + want: "http://example.com/test/st/v1/get-consistency-proof", + }, + { + endpoint: EndpointGetProofByHash, + want: "http://example.com/test/st/v1/get-proof-by-hash", + }, + { + endpoint: EndpointGetEntries, + want: "http://example.com/test/st/v1/get-entries", + }, + } { + if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) + } + if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { + t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) + } + } +} + +// TODO: TestWriteOctetResponse +func TestWriteOctetResponse(t *testing.T) { +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..af0c837 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/system-transparency/stfe + +go 1.16 + +require ( + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b + github.com/golang/mock v1.4.4 + github.com/google/certificate-transparency-go v1.1.1 + github.com/google/trillian v1.3.13 + github.com/prometheus/client_golang v1.9.0 // indirect + google.golang.org/grpc v1.35.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7a22d0c --- /dev/null +++ b/go.sum @@ -0,0 +1,771 @@ +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= +cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/beam v2.27.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.1 h1:6JHXZhXEvilMcTjR4MGZn5KV0IRkcFl4CJx5iHVhjFE= +github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= +github.com/google/trillian v1.3.13 h1:V0avmojBPY7YAlcd/nUVvNRprU28tRsahPNxIedqekU= +github.com/google/trillian v1.3.13/go.mod h1:8y3zC8XuqFxsslWPkP0r3sprERfFf7hCWmicL0yHZNI= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df h1:HWF6nM8ruGdu1K8IXFR+i2oT3YP+iBfZzCbC9zUfcWo= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/handler.go b/handler.go deleted file mode 100644 index 8c7e332..0000000 --- a/handler.go +++ /dev/null @@ -1,245 +0,0 @@ -package stfe - -import ( - "context" - "fmt" - "time" - - "net/http" - - "github.com/golang/glog" - "github.com/google/trillian" -) - -// Handler implements the http.Handler interface, and contains a reference -// to an STFE server instance as well as a function that uses it. -type Handler struct { - instance *Instance // STFE server instance - endpoint Endpoint // e.g., add-entry - method string // e.g., GET - 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 { - return h.endpoint.Path("", h.instance.LogParameters.Prefix) -} - -// 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.LogParameters.id(), string(a.endpoint), fmt.Sprintf("%d", statusCode)) - latency.Observe(time.Now().Sub(now).Seconds(), a.instance.LogParameters.id(), string(a.endpoint), fmt.Sprintf("%d", statusCode)) - }() - reqcnt.Inc(a.instance.LogParameters.id(), string(a.endpoint)) - - ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.instance.LogParameters.Deadline)) - defer cancel() - - if r.Method != a.method { - glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.instance.LogParameters.Prefix, string(a.endpoint), r.Method, a.method) - a.sendHTTPError(w, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed: %s", r.Method)) - return - } - - statusCode, err := a.handler(ctx, a.instance, w, r) - if err != nil { - glog.Warningf("handler error %s/%s: %v", a.instance.LogParameters.Prefix, a.endpoint, err) - a.sendHTTPError(w, statusCode, err) - } -} - -func (a Handler) sendHTTPError(w http.ResponseWriter, statusCode int, err error) { - http.Error(w, http.StatusText(statusCode), statusCode) -} - -// addEntry accepts log entries from trusted submitters -func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling add-entry request") - req, err := i.LogParameters.newAddEntryRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.QueueLeaf(ctx, &trillian.QueueLeafRequest{ - LogId: i.LogParameters.TreeId, - Leaf: &trillian.LogLeaf{ - LeafValue: req.Item, - ExtraData: req.Signature, - }, - }) - if errInner := checkQueueLeaf(trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad QueueLeafResponse: %v", errInner) - } - - sdi, err := i.LogParameters.genV1Sdi(trsp.QueuedLeaf.Leaf.LeafValue) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("failed creating signed debug info: %v", err) - } - rsp, err := sdi.MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getEntries provides a list of entries from the Trillian backend -func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-entries request") - req, err := i.LogParameters.newGetEntriesRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ - LogId: i.LogParameters.TreeId, - StartIndex: req.Start, - Count: req.End - req.Start + 1, - }) - if status, errInner := checkGetLeavesByRange(req, trsp, err); errInner != nil { - return status, fmt.Errorf("bad GetLeavesByRangeResponse: %v", errInner) - } - - rsp, err := i.LogParameters.newGetEntriesResponse(trsp.Leaves) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("failed creating GetEntriesResponse: %v", err) - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getAnchors provides a list of configured trust anchors -func getAnchors(_ context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-anchors request") - data := i.LogParameters.newGetAnchorsResponse() - if err := writeJsonResponse(data, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getProofByHash provides an inclusion proof based on a given leaf hash -func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-proof-by-hash request") - req, err := i.LogParameters.newGetProofByHashRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ - LogId: i.LogParameters.TreeId, - LeafHash: req.Hash, - TreeSize: req.TreeSize, - OrderBySequence: true, - }) - if errInner := checkGetInclusionProofByHash(i.LogParameters, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad GetInclusionProofByHashResponse: %v", errInner) - } - - rsp, err := NewInclusionProofV1(i.LogParameters.LogId, uint64(req.TreeSize), uint64(trsp.Proof[0].LeafIndex), trsp.Proof[0].Hashes).MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getConsistencyProof provides a consistency proof between two STHs -func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling get-consistency-proof request") - req, err := i.LogParameters.newGetConsistencyProofRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - - trsp, err := i.Client.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ - LogId: i.LogParameters.TreeId, - FirstTreeSize: int64(req.First), - SecondTreeSize: int64(req.Second), - }) - if errInner := checkGetConsistencyProof(i.LogParameters, trsp, err); errInner != nil { - return http.StatusInternalServerError, fmt.Errorf("bad GetConsistencyProofResponse: %v", errInner) - } - - rsp, err := NewConsistencyProofV1(i.LogParameters.LogId, uint64(req.First), uint64(req.Second), trsp.Proof.Hashes).MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getSth provides the most recent STH -func getSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-sth request") - sth, err := i.SthSource.Latest(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) - } - rsp, err := sth.MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getStableSth provides an STH that is stable for a fixed period of time -func getStableSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - glog.V(3).Info("handling get-stable-sth request") - sth, err := i.SthSource.Stable(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Latest: %v", err) - } - rsp, err := sth.MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// getCosi provides a cosigned STH -func getCosi(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { - costh, err := i.SthSource.Cosigned(ctx) - if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Cosigned: %v", err) - } - rsp, err := costh.MarshalB64() - if err != nil { - return http.StatusInternalServerError, err - } - if err := writeJsonResponse(rsp, w); err != nil { - return http.StatusInternalServerError, err - } - return http.StatusOK, nil -} - -// addCosi accepts cosigned STHs from trusted witnesses -func addCosi(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { - glog.V(3).Info("handling add-cosignature request") - costh, err := i.LogParameters.newAddCosignatureRequest(r) - if err != nil { - return http.StatusBadRequest, err - } - if err := i.SthSource.AddCosignature(ctx, costh); err != nil { - return http.StatusBadRequest, err - } - return http.StatusOK, nil -} 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{}") -} diff --git a/instance.go b/instance.go index c729bc8..6f1314b 100644 --- a/instance.go +++ b/instance.go @@ -1,123 +1,69 @@ package stfe import ( - "crypto" + "context" "fmt" - "strings" "time" "net/http" + "github.com/golang/glog" "github.com/google/trillian" - "github.com/system-transparency/stfe/namespace" ) -// Instance is an instance of a particular log front-end +// Instance is an instance of the system transparency front-end type Instance struct { Client trillian.TrillianLogClient - SthSource SthSource LogParameters *LogParameters + SthSource SthSource } -// LogParameters is a collection of log parameters -type LogParameters struct { - LogId []byte // used externally by everyone - TreeId int64 // used internally by Trillian - Prefix string // e.g., "test" for /test - MaxRange int64 // max entries per get-entries request - Submitters *namespace.NamespacePool // trusted submitters - Witnesses *namespace.NamespacePool // trusted witnesses - Deadline time.Duration // gRPC deadline - Interval time.Duration // cosigning sth frequency - Signer crypto.Signer // interface to access private key - HashType crypto.Hash // hash function used by Trillian +// Handlers returns a list of STFE handlers +func (i *Instance) Handlers() []Handler { + return []Handler{ + Handler{Instance: i, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, + Handler{Instance: i, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, + Handler{Instance: i, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, + Handler{Instance: i, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, + Handler{Instance: i, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, + Handler{Instance: i, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, + Handler{Instance: i, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, + Handler{Instance: i, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + } } -// Endpoint is a named HTTP API endpoint -type Endpoint string - -const ( - EndpointAddEntry = Endpoint("add-entry") - EndpointAddCosignature = Endpoint("add-cosignature") - EndpointGetEntries = Endpoint("get-entries") - EndpointGetAnchors = Endpoint("get-anchors") - EndpointGetProofByHash = Endpoint("get-proof-by-hash") - EndpointGetConsistencyProof = Endpoint("get-consistency-proof") - EndpointGetLatestSth = Endpoint("get-latest-sth") - EndpointGetStableSth = Endpoint("get-stable-sth") - EndpointGetCosignedSth = Endpoint("get-cosigned-sth") -) - -func (i Instance) String() string { - return fmt.Sprintf("%s\n", i.LogParameters) +// Handler implements the http.Handler interface, and contains a reference +// to an STFE server instance as well as a function that uses it. +type Handler struct { + Instance *Instance + Endpoint Endpoint + Method string + Handler func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) } -func (lp LogParameters) String() string { - return fmt.Sprintf("LogId(%s) TreeId(%d) Prefix(%s) MaxRange(%d) Submitters(%d) Witnesses(%d) Deadline(%v) Interval(%v)", lp.id(), lp.TreeId, lp.Prefix, lp.MaxRange, len(lp.Submitters.List()), len(lp.Witnesses.List()), lp.Deadline, lp.Interval) -} +// 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(string(a.Instance.LogParameters.LogIdBytes), string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + latency.Observe(time.Now().Sub(now).Seconds(), string(a.Instance.LogParameters.LogIdBytes), string(a.Endpoint), fmt.Sprintf("%d", statusCode)) + }() + reqcnt.Inc(string(a.Instance.LogParameters.LogIdBytes), string(a.Endpoint)) -func (e Endpoint) String() string { - return string(e) -} + ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.LogParameters.Deadline)) + defer cancel() -// NewInstance creates a new STFE instance -func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, source SthSource) *Instance { - return &Instance{ - LogParameters: lp, - Client: client, - SthSource: source, + if r.Method != a.Method { + glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.LogParameters.Prefix, string(a.Endpoint), r.Method, a.Method) + http.Error(w, "", http.StatusMethodNotAllowed) + return } -} -// NewLogParameters creates new log parameters. Note that the signer is -// assumed to be an ed25519 signing key. Could be fixed at some point. -func NewLogParameters(signer crypto.Signer, logId *namespace.Namespace, treeId int64, prefix string, submitters, witnesses *namespace.NamespacePool, maxRange int64, interval, deadline time.Duration) (*LogParameters, error) { - if signer == nil { - return nil, fmt.Errorf("need a signer but got none") - } - if maxRange < 1 { - return nil, fmt.Errorf("max range must be at least one") - } - lid, err := logId.Marshal() + statusCode, err := a.Handler(ctx, a.Instance, w, r) if err != nil { - return nil, fmt.Errorf("failed encoding log identifier: %v", err) - } - return &LogParameters{ - LogId: lid, - TreeId: treeId, - Prefix: prefix, - MaxRange: maxRange, - Submitters: submitters, - Witnesses: witnesses, - Deadline: deadline, - Interval: interval, - Signer: signer, - HashType: crypto.SHA256, // STFE assumes RFC 6962 hashing - }, nil -} - -// Handlers returns a list of STFE handlers -func (i *Instance) Handlers() []Handler { - return []Handler{ - Handler{instance: i, handler: addEntry, endpoint: EndpointAddEntry, method: http.MethodPost}, - Handler{instance: i, handler: addCosi, endpoint: EndpointAddCosignature, method: http.MethodPost}, - Handler{instance: i, handler: getEntries, endpoint: EndpointGetEntries, method: http.MethodGet}, - Handler{instance: i, handler: getAnchors, endpoint: EndpointGetAnchors, method: http.MethodGet}, - Handler{instance: i, handler: getProofByHash, endpoint: EndpointGetProofByHash, method: http.MethodGet}, - Handler{instance: i, handler: getConsistencyProof, endpoint: EndpointGetConsistencyProof, method: http.MethodGet}, - Handler{instance: i, handler: getSth, endpoint: EndpointGetLatestSth, method: http.MethodGet}, - Handler{instance: i, handler: getStableSth, endpoint: EndpointGetStableSth, method: http.MethodGet}, - Handler{instance: i, handler: getCosi, endpoint: EndpointGetCosignedSth, method: http.MethodGet}, + glog.Warningf("handler error %s/%s: %v", a.Instance.LogParameters.Prefix, a.Endpoint, err) + http.Error(w, "", statusCode) } } - -// id formats the log's identifier as base64 -func (i *LogParameters) id() string { - return b64(i.LogId) -} - -// Path joins a number of components to form a full endpoint path, e.g., base -// ("example.com"), prefix ("st/v1"), and the endpoint itself ("get-sth"). -func (e Endpoint) Path(components ...string) string { - return strings.Join(append(components, string(e)), "/") -} diff --git a/instance_test.go b/instance_test.go index 0ceedb8..21ef808 100644 --- a/instance_test.go +++ b/instance_test.go @@ -1,121 +1,90 @@ package stfe import ( - "bytes" + "crypto" "testing" - "time" - "crypto" - "crypto/ed25519" + "net/http" + "net/http/httptest" - "github.com/system-transparency/stfe/namespace" - "github.com/system-transparency/stfe/namespace/testdata" + "github.com/golang/mock/gomock" + "github.com/google/certificate-transparency-go/trillian/mockclient" + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" ) -var ( - testLogId = append([]byte{0x00, 0x01, 0x20}, testdata.Ed25519Vk3...) - testTreeId = int64(0) - testMaxRange = int64(3) - testPrefix = "test" - testHashType = crypto.SHA256 - testSignature = make([]byte, 32) - testNodeHash = make([]byte, 32) - testMessage = []byte("test message") - testPackage = []byte("foobar") - testChecksum = make([]byte, 32) - testTreeSize = uint64(128) - testTreeSizeLarger = uint64(256) - testTimestamp = uint64(0) - testProof = [][]byte{ - testNodeHash, - testNodeHash, - } - testIndex = uint64(0) - testHashLen = 31 - testDeadline = time.Second * 5 - testInterval = time.Second * 10 -) +type testInstance struct { + ctrl *gomock.Controller + client *mockclient.MockTrillianLogClient + instance *Instance +} -// TestNewLogParamters checks that invalid ones are rejected and that a valid -// set of parameters are accepted. -func TestNewLogParameters(t *testing.T) { - testLogId := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk3) - namespaces := mustNewNamespacePool(t, []*namespace.Namespace{ - mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - }) - witnesses := mustNewNamespacePool(t, []*namespace.Namespace{}) - signer := ed25519.PrivateKey(testdata.Ed25519Sk) - for _, table := range []struct { - description string - logId *namespace.Namespace - maxRange int64 - signer crypto.Signer - wantErr bool - }{ - { - description: "invalid signer: nil", - logId: testLogId, - maxRange: testMaxRange, - signer: nil, - wantErr: true, - }, - { - description: "invalid max range", - logId: testLogId, - maxRange: 0, - signer: signer, - wantErr: true, - }, - { - description: "invalid log identifier", - logId: &namespace.Namespace{ - Format: namespace.NamespaceFormatEd25519V1, - NamespaceEd25519V1: &namespace.NamespaceEd25519V1{ - Namespace: make([]byte, 31), // too short - }, +// newTestInstances sets up a test instance that uses default log parameters +// with an optional signer, see newLogParameters() for further details. The +// SthSource is instantiated with an ActiveSthSource that has (i) the default +// STH as the currently cosigned STH based on testdata.Ed25519VkWitness, and +// (ii) the default STH without any cosignatures as the currently stable STH. +func newTestInstance(t *testing.T, signer crypto.Signer) *testInstance { + t.Helper() + ctrl := gomock.NewController(t) + client := mockclient.NewMockTrillianLogClient(ctrl) + return &testInstance{ + ctrl: ctrl, + client: client, + instance: &Instance{ + Client: client, + LogParameters: newLogParameters(t, signer), + SthSource: &ActiveSthSource{ + client: client, + logParameters: newLogParameters(t, signer), + currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), }, - maxRange: testMaxRange, - signer: signer, - wantErr: true, }, - { - description: "valid log parameters", - logId: testLogId, - maxRange: testMaxRange, - signer: signer, - }, - } { - lp, err := NewLogParameters(table.signer, table.logId, testTreeId, testPrefix, namespaces, witnesses, table.maxRange, testInterval, testDeadline) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error=%v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - lid, err := table.logId.Marshal() - if err != nil { - t.Fatalf("must marshal log id: %v", err) - } + } +} - if got, want := lp.LogId, lid; !bytes.Equal(got, want) { - t.Errorf("got log id %X but wanted %X in test %q", got, want, table.description) - } - if got, want := lp.TreeId, testTreeId; got != want { - t.Errorf("got tree id %d but wanted %d in test %q", got, want, table.description) - } - if got, want := lp.Prefix, testPrefix; got != want { - t.Errorf("got prefix %s but wanted %s in test %q", got, want, table.description) - } - if got, want := lp.MaxRange, testMaxRange; got != want { - t.Errorf("got max range %d but wanted %d in test %q", got, want, table.description) - } - if got, want := len(lp.Submitters.List()), len(namespaces.List()); got != want { - t.Errorf("got %d anchors but wanted %d in test %q", got, want, table.description) - } - if got, want := len(lp.Witnesses.List()), len(witnesses.List()); got != want { - t.Errorf("got %d anchors but wanted %d in test %q", got, want, table.description) - } +// getHandlers returns all endpoints that use HTTP GET as a map to handlers +func (ti *testInstance) getHandlers(t *testing.T) map[Endpoint]Handler { + t.Helper() + return map[Endpoint]Handler{ + EndpointGetLatestSth: Handler{Instance: ti.instance, Handler: getLatestSth, Endpoint: EndpointGetLatestSth, Method: http.MethodGet}, + EndpointGetStableSth: Handler{Instance: ti.instance, Handler: getStableSth, Endpoint: EndpointGetStableSth, Method: http.MethodGet}, + EndpointGetCosignedSth: Handler{Instance: ti.instance, Handler: getCosignedSth, Endpoint: EndpointGetCosignedSth, Method: http.MethodGet}, + } +} + +// postHandlers returns all endpoints that use HTTP POST as a map to handlers +func (ti *testInstance) postHandlers(t *testing.T) map[Endpoint]Handler { + t.Helper() + return map[Endpoint]Handler{ + EndpointAddEntry: Handler{Instance: ti.instance, Handler: addEntry, Endpoint: EndpointAddEntry, Method: http.MethodPost}, + EndpointAddCosignature: Handler{Instance: ti.instance, Handler: addCosignature, Endpoint: EndpointAddCosignature, Method: http.MethodPost}, + EndpointGetConsistencyProof: Handler{Instance: ti.instance, Handler: getConsistencyProof, Endpoint: EndpointGetConsistencyProof, Method: http.MethodPost}, + EndpointGetProofByHash: Handler{Instance: ti.instance, Handler: getProofByHash, Endpoint: EndpointGetProofByHash, Method: http.MethodPost}, + EndpointGetEntries: Handler{Instance: ti.instance, Handler: getEntries, Endpoint: EndpointGetEntries, Method: http.MethodPost}, + } +} + +// getHandler must return a particular HTTP GET handler +func (ti *testInstance) getHandler(t *testing.T, endpoint Endpoint) Handler { + t.Helper() + handler, ok := ti.getHandlers(t)[endpoint] + if !ok { + t.Fatalf("must return HTTP GET handler for endpoint: %s", endpoint) + } + return handler +} + +// postHandler must return a particular HTTP POST handler +func (ti *testInstance) postHandler(t *testing.T, endpoint Endpoint) Handler { + t.Helper() + handler, ok := ti.postHandlers(t)[endpoint] + if !ok { + t.Fatalf("must return HTTP POST handler for endpoint: %s", endpoint) } + return handler } // TestHandlers checks that we configured all endpoints and that there are no @@ -123,21 +92,20 @@ func TestNewLogParameters(t *testing.T) { func TestHandlers(t *testing.T) { endpoints := map[Endpoint]bool{ EndpointAddEntry: false, - EndpointGetEntries: false, + EndpointAddCosignature: false, EndpointGetLatestSth: false, - EndpointGetProofByHash: false, - EndpointGetConsistencyProof: false, - EndpointGetAnchors: false, EndpointGetStableSth: false, EndpointGetCosignedSth: false, - EndpointAddCosignature: false, + EndpointGetConsistencyProof: false, + EndpointGetProofByHash: false, + EndpointGetEntries: false, } - i := NewInstance(makeTestLogParameters(t, nil), nil, nil) + i := &Instance{nil, newLogParameters(t, nil), nil} for _, handler := range i.Handlers() { - if _, ok := endpoints[handler.endpoint]; !ok { - t.Errorf("got unexpected endpoint: %s", handler.endpoint) + if _, ok := endpoints[handler.Endpoint]; !ok { + t.Errorf("got unexpected endpoint: %s", handler.Endpoint) } - endpoints[handler.endpoint] = true + endpoints[handler.Endpoint] = true } for endpoint, ok := range endpoints { if !ok { @@ -146,104 +114,42 @@ func TestHandlers(t *testing.T) { } } -// TestEndpointPath checks that the endpoint path builder works as expected -func TestEndpointPath(t *testing.T) { - base, prefix := "http://example.com", "test" - for _, table := range []struct { - endpoint Endpoint - want string - }{ - { - endpoint: EndpointAddEntry, - want: "http://example.com/test/add-entry", - }, - { - endpoint: EndpointGetEntries, - want: "http://example.com/test/get-entries", - }, - { - endpoint: EndpointGetProofByHash, - want: "http://example.com/test/get-proof-by-hash", - }, - { - endpoint: EndpointGetConsistencyProof, - want: "http://example.com/test/get-consistency-proof", - }, - { - endpoint: EndpointGetLatestSth, - want: "http://example.com/test/get-latest-sth", - }, - { - endpoint: EndpointGetAnchors, - want: "http://example.com/test/get-anchors", - }, - { - endpoint: EndpointGetStableSth, - want: "http://example.com/test/get-stable-sth", - }, - { - endpoint: EndpointGetCosignedSth, - want: "http://example.com/test/get-cosigned-sth", - }, - { - endpoint: EndpointAddCosignature, - want: "http://example.com/test/add-cosignature", - }, - } { - if got, want := table.endpoint.Path(base, prefix), table.want; got != want { - t.Errorf("got %s but wanted %s with multiple components", got, want) - } - if got, want := table.endpoint.Path(base+"/"+prefix), table.want; got != want { - t.Errorf("got %s but wanted %s with one component", got, want) - } - } -} +// TestGetHandlersRejectPost checks that all get handlers reject post requests +func TestGetHandlersRejectPost(t *testing.T) { + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() -func mustNewLogId(t *testing.T, namespace *namespace.Namespace) []byte { - b, err := namespace.Marshal() - if err != nil { - t.Fatalf("must marshal log id: %v", err) - } - return b -} + for endpoint, handler := range ti.getHandlers(t) { + t.Run(string(endpoint), func(t *testing.T) { + s := httptest.NewServer(handler) + defer s.Close() -func mustNewNamespaceEd25519V1(t *testing.T, vk []byte) *namespace.Namespace { - namespace, err := namespace.NewNamespaceEd25519V1(vk) - if err != nil { - t.Fatalf("must make ed25519 namespace: %v", err) + url := endpoint.Path(s.URL, ti.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) + } + }) } - return namespace } -func mustNewNamespacePool(t *testing.T, anchors []*namespace.Namespace) *namespace.NamespacePool { - namespaces, err := namespace.NewNamespacePool(anchors) - if err != nil { - t.Fatalf("must make namespaces: %v", err) - } - return namespaces -} +// TestPostHandlersRejectGet checks that all post handlers reject get requests +func TestPostHandlersRejectGet(t *testing.T) { + ti := newTestInstance(t, nil) + defer ti.ctrl.Finish() + + for endpoint, handler := range ti.postHandlers(t) { + t.Run(string(endpoint), func(t *testing.T) { + s := httptest.NewServer(handler) + defer s.Close() -// makeTestLogParameters makes a collection of test log parameters. -// -// The log's identity is based on testdata.Ed25519{Vk3,Sk3}. The log's accepted -// submitters are based on testdata.Ed25519Vk. The log's accepted witnesses are -// based on testdata.Ed25519Vk. The remaining log parameters are based on the -// global test* variables in this file. -// -// For convenience the passed signer is optional (i.e., it may be nil). -func makeTestLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { - return &LogParameters{ - LogId: mustNewLogId(t, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk3)), - TreeId: testTreeId, - Prefix: testPrefix, - MaxRange: testMaxRange, - Submitters: mustNewNamespacePool(t, []*namespace.Namespace{ - mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - }), - Witnesses: mustNewNamespacePool(t, []*namespace.Namespace{ - mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - }), - Signer: signer, - HashType: testHashType, + url := endpoint.Path(s.URL, ti.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) + } + }) } } diff --git a/log_parameters.go b/log_parameters.go new file mode 100644 index 0000000..86ac0cc --- /dev/null +++ b/log_parameters.go @@ -0,0 +1,71 @@ +package stfe + +import ( + "crypto" + "fmt" + "time" + + "crypto/rand" + + "github.com/system-transparency/stfe/types" +) + +// LogParameters is a collection of log parameters +type LogParameters struct { + LogId *types.Namespace // log identifier + LogIdBytes []byte // serialized log id + TreeId int64 // used internally by Trillian + Prefix string // e.g., "test" for /test + MaxRange int64 // max entries per get-entries request + Submitters *types.NamespacePool // trusted submitters + Witnesses *types.NamespacePool // trusted witnesses + Deadline time.Duration // gRPC deadline + Interval time.Duration // cosigning sth frequency + HashType crypto.Hash // hash function used by Trillian + Signer crypto.Signer // access to Ed25519 private key +} + +// NewLogParameters creates newly initialized log parameters +func NewLogParameters(signer crypto.Signer, logId *types.Namespace, treeId int64, prefix string, submitters, witnesses *types.NamespacePool, maxRange int64, interval, deadline time.Duration) (*LogParameters, error) { + logIdBytes, err := types.Marshal(*logId) + if err != nil { + return nil, fmt.Errorf("Marshal failed for log identifier: %v", err) + } + return &LogParameters{ + LogId: logId, + TreeId: treeId, + Prefix: prefix, + MaxRange: maxRange, + Submitters: submitters, + Witnesses: witnesses, + Deadline: deadline, + Interval: interval, + HashType: crypto.SHA256, + Signer: signer, + LogIdBytes: logIdBytes, + }, nil +} + +// SignTreeHeadV1 signs a TreeHeadV1 structure +func (lp *LogParameters) SignTreeHeadV1(th *types.TreeHeadV1) (*types.StItem, error) { + serialized, err := types.Marshal(*th) + if err != nil { + return nil, fmt.Errorf("Marshal failed for TreeHeadV1: %v", err) + } + sig, err := lp.Signer.Sign(rand.Reader, serialized, crypto.Hash(0)) + if err != nil { + return nil, fmt.Errorf("Sign failed: %v", err) + } + lastSthTimestamp.Set(float64(time.Now().Unix()), string(lp.LogIdBytes)) + lastSthSize.Set(float64(th.TreeSize), string(lp.LogIdBytes)) + return &types.StItem{ + Format: types.StFormatSignedTreeHeadV1, + SignedTreeHeadV1: &types.SignedTreeHeadV1{ + TreeHead: *th, + Signature: types.SignatureV1{ + Namespace: *lp.LogId, + Signature: sig, + }, + }, + }, nil +} diff --git a/log_parameters_test.go b/log_parameters_test.go new file mode 100644 index 0000000..cec7674 --- /dev/null +++ b/log_parameters_test.go @@ -0,0 +1,98 @@ +package stfe + +import ( + "crypto" + "fmt" + "reflect" + "testing" + + cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" +) + +// newLogParameters must create new log parameters with an optional log signer +// based on the parameters in "github.com/system-transparency/stfe/testdata". +// The log's namespace is initialized with testdata.LogEd25519Vk, the submmiter +// namespace list is initialized with testdata.SubmmiterEd25519, and the witness +// namespace list is initialized with testdata.WitnessEd25519Vk. +func newLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { + t.Helper() + logId := testdata.NewNamespace(t, testdata.Ed25519VkLog) + witnessPool := testdata.NewNamespacePool(t, []*types.Namespace{ + testdata.NewNamespace(t, testdata.Ed25519VkWitness), + }) + submitPool := testdata.NewNamespacePool(t, []*types.Namespace{ + testdata.NewNamespace(t, testdata.Ed25519VkSubmitter), + }) + lp, err := NewLogParameters(signer, logId, testdata.TreeId, testdata.Prefix, submitPool, witnessPool, testdata.MaxRange, testdata.Interval, testdata.Deadline) + if err != nil { + t.Fatalf("must create new log parameters: %v", err) + } + return lp +} + +func TestNewLogParameters(t *testing.T) { + for _, table := range []struct { + description string + logId *types.Namespace + wantErr bool + }{ + { + description: "invalid: cannot marshal log id", + logId: &types.Namespace{ + Format: types.NamespaceFormatReserved, + }, + wantErr: true, + }, + { + description: "valid", + logId: testdata.NewNamespace(t, testdata.Ed25519VkLog), + }, + } { + _, err := NewLogParameters(nil, table.logId, testdata.TreeId, testdata.Prefix, nil, nil, testdata.MaxRange, testdata.Interval, testdata.Deadline) + 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) + } + } +} + +func TestSignTreeHeadV1(t *testing.T) { + for _, table := range []struct { + description string + th *types.TreeHeadV1 + signer crypto.Signer + wantErr bool + wantSth *types.StItem + }{ + { + description: "invalid: marshal failure", + th: types.NewTreeHeadV1(testdata.Timestamp, testdata.TreeSize, nil, testdata.Extension), + wantErr: true, + }, + { + description: "invalid: signature failure", + th: types.NewTreeHeadV1(testdata.Timestamp, testdata.TreeSize, testdata.NodeHash, testdata.Extension), + signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signer failed")), + wantErr: true, + }, + { + description: "valid", + th: testdata.DefaultTh(t), + signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), + wantSth: testdata.DefaultSth(t, testdata.Ed25519VkLog), + }, + } { + sth, err := newLogParameters(t, table.signer).SignTreeHeadV1(table.th) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + + if got, want := sth, table.wantSth; !reflect.DeepEqual(got, want) { + t.Errorf("got \n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) + } + } +} diff --git a/metric.go b/metric.go index 1d0f100..7e3e8b2 100644 --- a/metric.go +++ b/metric.go @@ -9,7 +9,6 @@ var ( reqcnt monitoring.Counter // number of incoming http requests rspcnt monitoring.Counter // number of valid http responses latency monitoring.Histogram // request-response latency - lastSdiTimestamp monitoring.Gauge // unix timestamp from the most recent sdi lastSthTimestamp monitoring.Gauge // unix timestamp from the most recent sth lastSthSize monitoring.Gauge // tree size of most recent sth ) @@ -20,6 +19,5 @@ func init() { rspcnt = mf.NewCounter("http_rsp", "number of http requests", "logid", "endpoint", "status") latency = mf.NewHistogram("http_latency", "http request-response latency", "logid", "endpoint", "status") lastSthTimestamp = mf.NewGauge("last_sth_timestamp", "unix timestamp while handling the most recent sth", "logid") - lastSdiTimestamp = mf.NewGauge("last_sdi_timestamp", "unix timestamp while handling the most recent sdi", "logid") lastSthSize = mf.NewGauge("last_sth_size", "most recent sth tree size", "logid") } diff --git a/namespace/namespace.go b/namespace/namespace.go deleted file mode 100644 index f5c56cf..0000000 --- a/namespace/namespace.go +++ /dev/null @@ -1,150 +0,0 @@ -// Package namespace provides namespace functionality. A namespace refers to a -// particular verification key and signing algorithm that can be serialized with -// TLS 1.2 notation, see RFC 5246 (§4). Only Ed25519 is supported at this time. -// -// For example, this is how a serialized Ed25519 namespace looks like: -// -// 0 2 3 35 (byte index) -// +----+-----------------------+ -// | 1 + 32 + Verification key + -// +----+-----------------------+ -package namespace - -import ( - "fmt" - - "crypto/ed25519" - - "github.com/google/certificate-transparency-go/tls" -) - -// NamespaceFormat defines a particular namespace type that is versioend -type NamespaceFormat tls.Enum - -const ( - NamespaceFormatReserved NamespaceFormat = 0 - NamespaceFormatEd25519V1 NamespaceFormat = 1 -) - -// Namespace references a versioned namespace based on a given format specifier -type Namespace struct { - Format NamespaceFormat `tls:"maxval:65535"` - NamespaceEd25519V1 *NamespaceEd25519V1 `tls:"selector:Format,val:1"` -} - -// NamespaceEd25519V1 uses an Ed25519 verification key as namespace. Encoding, -// signing, and verification operations are defined by RFC 8032. -type NamespaceEd25519V1 struct { - Namespace []byte `tls:"minlen:32,maxlen:32"` -} - -// String returns a human-readable representation of a namespace. -func (n Namespace) String() string { - switch n.Format { - case NamespaceFormatEd25519V1: - return fmt.Sprintf("%x", n.NamespaceEd25519V1.Namespace) - default: - return "reserved" - } -} - -// NewNamespaceEd25519V1 returns an new Ed25519V1 namespace based on a -// verification key. -func NewNamespaceEd25519V1(vk []byte) (*Namespace, error) { - if len(vk) != 32 { - return nil, fmt.Errorf("invalid verification key: must be 32 bytes") - } - return &Namespace{ - Format: NamespaceFormatEd25519V1, - NamespaceEd25519V1: &NamespaceEd25519V1{ - Namespace: vk, - }, - }, nil -} - -// Verify checks that signature is valid over message for this namespace -func (ns *Namespace) Verify(message, signature []byte) error { - switch ns.Format { - case NamespaceFormatEd25519V1: - if !ed25519.Verify(ed25519.PublicKey(ns.NamespaceEd25519V1.Namespace), message, signature) { - return fmt.Errorf("ed25519 signature verification failed") - } - default: - return fmt.Errorf("namespace not supported: %v", ns.Format) - } - return nil -} - -func (ns *Namespace) Marshal() ([]byte, error) { - serialized, err := tls.Marshal(*ns) - if err != nil { - return nil, fmt.Errorf("marshaled failed for namespace(%v): %v", ns.Format, err) - } - return serialized, err -} - -func (ns *Namespace) Unmarshal(serialized []byte) error { - extra, err := tls.Unmarshal(serialized, ns) - if err != nil { - return fmt.Errorf("unmarshal failed for namespace: %v", err) - } else if len(extra) > 0 { - return fmt.Errorf("unmarshal found extra data for namespace(%v): %v", ns.Format, err) - } - return nil -} - -// NamespacePool is a pool of namespaces that contain complete verification keys -type NamespacePool struct { - pool map[string]*Namespace - list []*Namespace - // If we need to update this structure without a restart => add mutex. -} - -// NewNameSpacePool creates a new namespace pool from a list of namespaces. An -// error is returned if there are duplicate namespaces or namespaces without a -// complete verification key. The latter is determined by namespaceWithKey(). -func NewNamespacePool(namespaces []*Namespace) (*NamespacePool, error) { - np := &NamespacePool{ - pool: make(map[string]*Namespace), - list: make([]*Namespace, 0), - } - for _, namespace := range namespaces { - if !namespaceWithKey(namespace.Format) { - return nil, fmt.Errorf("need verification key in namespace pool: %v", namespace.Format) - } - if _, ok := np.pool[namespace.String()]; ok { - return nil, fmt.Errorf("duplicate namespace: %v", namespace.String()) - } - np.pool[namespace.String()] = namespace - np.list = append(np.list, namespace) - } - return np, nil -} - -// Find checks if namespace is a member of the namespace pool. -func (np *NamespacePool) Find(namespace *Namespace) (*Namespace, bool) { - if _, ok := np.pool[namespace.String()]; !ok { - return nil, false - } - // If the passed namespace is a key fingerprint the actual key needs to be - // attached before returning. Not applicable for Ed25519. Docdoc later. - return namespace, true -} - -// List returns a copied list of namespaces that is used by this pool. -func (np *NamespacePool) List() []*Namespace { - namespaces := make([]*Namespace, len(np.list)) - copy(namespaces, np.list) - return namespaces -} - -// namespaceWithKey returns true if a namespace format contains a complete -// verification key. I.e., some formats might have a key fingerprint instead. -func namespaceWithKey(format NamespaceFormat) bool { - switch format { - case NamespaceFormatEd25519V1: - return true - default: - return false - } -} diff --git a/namespace/namespace_test.go b/namespace/namespace_test.go deleted file mode 100644 index 10c0bf0..0000000 --- a/namespace/namespace_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package namespace - -import ( - "bytes" - "testing" - - "crypto/ed25519" - - "github.com/system-transparency/stfe/namespace/testdata" -) - -func TestNewNamespaceEd25519V1(t *testing.T) { - for _, table := range []struct { - description string - vk []byte - wantErr bool - }{ - { - description: "invalid", - vk: append(testdata.Ed25519Vk, 0x00), - wantErr: true, - }, - { - description: "valid", - vk: testdata.Ed25519Vk, - }, - } { - n, err := NewNamespaceEd25519V1(table.vk) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - if got, want := n.Format, NamespaceFormatEd25519V1; got != want { - t.Errorf("got namespace format %v but wanted %v in test %q", got, want, table.description) - continue - } - if got, want := n.NamespaceEd25519V1.Namespace, table.vk; !bytes.Equal(got, want) { - t.Errorf("got namespace %X but wanted %X in test %q", got, want, table.description) - } - } -} - -func TestVerify(t *testing.T) { - testMsg := []byte("msg") - for _, table := range []struct { - description string - namespace *Namespace - msg, sig []byte - wantErr bool - }{ - { - description: "invalid: unsupported namespace", - namespace: &Namespace{Format: NamespaceFormatReserved}, - msg: testMsg, - sig: []byte("sig"), - wantErr: true, - }, - { - description: "invalid: bad ed25519 verification key", - namespace: mustNewNamespaceEd25519V1(t, testdata.Ed25519Sk[:32]), - msg: testMsg, - sig: ed25519.Sign(ed25519.PrivateKey(testdata.Ed25519Sk), testMsg), - wantErr: true, - }, - { - description: "invalid: ed25519 signature is not over message", - namespace: mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - msg: append(testMsg, 0x00), - sig: ed25519.Sign(ed25519.PrivateKey(testdata.Ed25519Sk), testMsg), - wantErr: true, - }, - { - description: "valid: ed25519", - namespace: mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - msg: testMsg, - sig: ed25519.Sign(ed25519.PrivateKey(testdata.Ed25519Sk), testMsg), - }, - } { - err := table.namespace.Verify(table.msg, table.sig) - 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) - } - } -} - -func TestMarshal(t *testing.T) { - for _, table := range []struct { - description string - namespace *Namespace - wantErr bool - wantBytes []byte - }{ - { - description: "invalid ed25519: namespace size too small", - namespace: &Namespace{ - Format: NamespaceFormatEd25519V1, - NamespaceEd25519V1: &NamespaceEd25519V1{ - Namespace: testdata.Ed25519Vk[:len(testdata.Ed25519Vk)-1], - }, - }, - wantErr: true, - }, - { - description: "invalid ed25519: namespace size too large", - namespace: &Namespace{ - Format: NamespaceFormatEd25519V1, - NamespaceEd25519V1: &NamespaceEd25519V1{ - Namespace: append(testdata.Ed25519Vk, 0x00), - }, - }, - wantErr: true, - }, - { - description: "valid: ed25519", - namespace: mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - // TODO: wantBytes - }, - } { - _, err := table.namespace.Marshal() - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - // TODO: add check that we got the bytes we wanted also - } -} - -func TestUnmarshal(t *testing.T) { - // TODO -} - -func TestNewNamespacePool(t *testing.T) { - ns1, _ := NewNamespaceEd25519V1(testdata.Ed25519Vk) - ns2, _ := NewNamespaceEd25519V1(make([]byte, 32)) - nsr := &Namespace{Format: NamespaceFormatReserved} - for _, table := range []struct { - description string - namespaces []*Namespace - wantErr bool - }{ - { - description: "invalid: duplicate namespace", - namespaces: []*Namespace{ns1, ns1, ns2}, - wantErr: true, - }, - { - description: "invalid: namespace without key", - namespaces: []*Namespace{ns1, nsr, ns2}, - wantErr: true, - }, - { - description: "valid: empty", - namespaces: []*Namespace{}, - }, - { - description: "valid: one namespace", - namespaces: []*Namespace{ns1}, - }, - { - description: "valid: two namespaces", - namespaces: []*Namespace{ns1, ns2}, - }, - } { - _, err := NewNamespacePool(table.namespaces) - 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) - } - } -} - -func TestFind(t *testing.T) { - ns1 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk) - ns2 := mustNewNamespaceEd25519V1(t, make([]byte, 32)) - pool, _ := NewNamespacePool(nil) - _, got := pool.Find(ns1) - if want := false; got != want { - t.Errorf("got %v but wanted %v in test %q", got, want, "empty pool") - } - - pool, _ = NewNamespacePool([]*Namespace{ns1}) - _, got = pool.Find(ns1) - if want := true; got != want { - t.Errorf("got %v but wanted %v in test %q", got, want, "non-empty pool: looking for member") - } - _, got = pool.Find(ns2) - if want := false; got != want { - t.Errorf("got %v but wanted %v in test %q", got, want, "non-empty pool: looking for non-member") - } -} - -func TestList(t *testing.T) { - ns1 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk) - ns2 := mustNewNamespaceEd25519V1(t, make([]byte, 32)) - namespaces := []*Namespace{ns1, ns2} - pool, _ := NewNamespacePool(namespaces) - l1 := pool.List() - if got, want := len(l1), len(namespaces); got != want { - t.Errorf("got len %v but wanted %v", got, want) - } - - l1[0] = ns2 - l2 := pool.List() - if bytes.Equal(l1[0].NamespaceEd25519V1.Namespace, l2[0].NamespaceEd25519V1.Namespace) { - t.Errorf("returned list is not a copy") - } -} - -func mustNewNamespaceEd25519V1(t *testing.T, vk []byte) *Namespace { - namespace, err := NewNamespaceEd25519V1(vk) - if err != nil { - t.Fatalf("must make ed25519 namespace: %v", err) - } - return namespace -} diff --git a/namespace/testdata/data.go b/namespace/testdata/data.go deleted file mode 100644 index 5f3f4df..0000000 --- a/namespace/testdata/data.go +++ /dev/null @@ -1,21 +0,0 @@ -package testdata - -import ( - "encoding/base64" -) - -var ( - Ed25519Vk = mustDecodeB64("HOQFUkKNWpjYAhNKTyWCzahlI7RDtf5123kHD2LACj0=") - Ed25519Sk = mustDecodeB64("Zaajc50Xt1tNpTj6WYkljzcVjLXL2CcQcHFT/xZqYEcc5AVSQo1amNgCE0pPJYLNqGUjtEO1/nXbeQcPYsAKPQ==") - - Ed25519Vk2 = mustDecodeB64("LqrWb9JwQUTk/SwTNDdMH8aRmy3mbmhwEepO5WSgb+A=") - Ed25519Sk2 = mustDecodeB64("fDkSq4cWvG72yMhUyHVcZ72QKerZ66msgyVqDvfufZQuqtZv0nBBROT9LBM0N0wfxpGbLeZuaHAR6k7lZKBv4A==") - - Ed25519Vk3 = mustDecodeB64("Icd1U1oY0z+5iAwgCQZyGI+pycGs6GI2rQO8gAzT7Y0=") - Ed25519Sk3 = mustDecodeB64("Q6uNL7VKv9flaBIXBXy/NyhNOicLYKKmfuJ6tLReMvQhx3VTWhjTP7mIDCAJBnIYj6nJwazoYjatA7yADNPtjQ==") -) - -func mustDecodeB64(s string) []byte { - b, _ := base64.StdEncoding.DecodeString(s) - return b -} diff --git a/reqres.go b/reqres.go deleted file mode 100644 index e4ad11b..0000000 --- a/reqres.go +++ /dev/null @@ -1,228 +0,0 @@ -package stfe - -import ( - "fmt" - "strconv" - - "encoding/json" - "io/ioutil" - "net/http" - - "github.com/google/trillian" -) - -// AddEntryRequest is a collection of add-entry input parameters -type AddEntryRequest struct { - Item []byte `json:"item"` // tls-serialized StItem - Signature []byte `json:"signature"` // serialized signature using the signature scheme below -} - -// GetEntriesRequest is a collection of get-entry input parameters -type GetEntriesRequest struct { - Start int64 `json:"start"` // 0-based and inclusive start-index - End int64 `json:"end"` // 0-based and inclusive end-index -} - -// GetProofByHashRequest is a collection of get-proof-by-hash input parameters -type GetProofByHashRequest struct { - Hash []byte `json:"hash"` // leaf hash - TreeSize int64 `json:"tree_size"` // tree head size to base proof on -} - -// GetConsistencyProofRequest is a collection of get-consistency-proof input -// parameters -type GetConsistencyProofRequest struct { - First int64 `json:"first"` // size of the older Merkle tree - Second int64 `json:"second"` // size of the newer Merkle tree -} - -// GetEntryResponse is an assembled log entry and its associated appendix. It -// is identical to the add-entry request that the log once accepted. -type GetEntryResponse AddEntryRequest - -// AddCosignatureRequest encapsulates a cosignature request -type AddCosignatureRequest struct { - Item []byte `json:"item"` -} - -// newAddCosignatureRequest parses and verifies an STH cosignature request -func (lp *LogParameters) newAddCosignatureRequest(r *http.Request) (*StItem, error) { - var req AddCosignatureRequest - if err := unpackJsonPost(r, &req); err != nil { - return nil, fmt.Errorf("unpackJsonPost: %v", err) - } - - // Try decoding as CosignedTreeHeadV1 - var item StItem - if err := item.Unmarshal(req.Item); err != nil { - return nil, fmt.Errorf("Unmarshal: %v", err) - } - if item.Format != StFormatCosignedTreeHeadV1 { - return nil, fmt.Errorf("invalid StItem format: %v", item.Format) - } - - // Check that witness namespace is valid - sth := &StItem{Format: StFormatSignedTreeHeadV1, SignedTreeHeadV1: &item.CosignedTreeHeadV1.SignedTreeHeadV1} - if len(item.CosignedTreeHeadV1.SignatureV1) != 1 { - return nil, fmt.Errorf("invalid number of cosignatures") - } else if namespace, ok := lp.Witnesses.Find(&item.CosignedTreeHeadV1.SignatureV1[0].Namespace); !ok { - return nil, fmt.Errorf("unknown witness") - } else if msg, err := sth.Marshal(); err != nil { - return nil, fmt.Errorf("Marshal: %v", err) - } else if err := namespace.Verify(msg, item.CosignedTreeHeadV1.SignatureV1[0].Signature); err != nil { - return nil, fmt.Errorf("Verify: %v", err) - } - return &item, nil -} - -// newAddEntryRequest parses and sanitizes the JSON-encoded add-entry -// parameters from an incoming HTTP post. The request is returned if it is -// a checksumV1 entry that is signed by a valid namespace. -func (lp *LogParameters) newAddEntryRequest(r *http.Request) (*AddEntryRequest, error) { - var entry AddEntryRequest - if err := unpackJsonPost(r, &entry); err != nil { - return nil, err - } - - // Try decoding as ChecksumV1 StItem - var item StItem - if err := item.Unmarshal(entry.Item); err != nil { - return nil, fmt.Errorf("StItem(%s): %v", item.Format, err) - } - if item.Format != StFormatChecksumV1 { - return nil, fmt.Errorf("invalid StItem format: %s", item.Format) - } - - // Check that namespace is valid for item - if namespace, ok := lp.Submitters.Find(&item.ChecksumV1.Namespace); !ok { - return nil, fmt.Errorf("unknown namespace: %s", item.ChecksumV1.Namespace.String()) - } else if err := namespace.Verify(entry.Item, entry.Signature); err != nil { - return nil, fmt.Errorf("invalid namespace: %v", err) - } - return &entry, nil -} - -// newGetEntriesRequest parses and sanitizes the URL-encoded get-entries -// parameters from an incoming HTTP request. Too large ranges are truncated -// based on the log's configured max range, but without taking the log's -// current tree size into consideration (because it is not know at this point). -func (lp *LogParameters) newGetEntriesRequest(httpRequest *http.Request) (*GetEntriesRequest, error) { - start, err := strconv.ParseInt(httpRequest.FormValue("start"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad start parameter: %v", err) - } - end, err := strconv.ParseInt(httpRequest.FormValue("end"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad end parameter: %v", err) - } - - if start < 0 { - return nil, fmt.Errorf("bad parameters: start(%v) must have a non-negative value", start) - } - if start > end { - return nil, fmt.Errorf("bad parameters: start(%v) must be less than or equal to end(%v)", start, end) - } - if end-start+1 > lp.MaxRange { - end = start + lp.MaxRange - 1 - } - return &GetEntriesRequest{Start: start, End: end}, nil -} - -// newGetProofByHashRequest parses and sanitizes the URL-encoded -// get-proof-by-hash parameters from an incoming HTTP request. -func (lp *LogParameters) newGetProofByHashRequest(httpRequest *http.Request) (*GetProofByHashRequest, error) { - size, err := strconv.ParseInt(httpRequest.FormValue("tree_size"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad tree_size parameter: %v", err) - } - if size < 1 { - return nil, fmt.Errorf("bad tree_size parameter: must be larger than zero") - } - hash, err := deb64(httpRequest.FormValue("hash")) - if err != nil { - return nil, fmt.Errorf("bad hash parameter: %v", err) - } - if len(hash) != lp.HashType.Size() { - return nil, fmt.Errorf("bad hash parameter: must be %d bytes", lp.HashType.Size()) - } - return &GetProofByHashRequest{TreeSize: size, Hash: hash}, nil -} - -// newGetConsistencyProofRequest parses and sanitizes the URL-encoded -// get-consistency-proof-request parameters from an incoming HTTP request -func (lp *LogParameters) newGetConsistencyProofRequest(httpRequest *http.Request) (*GetConsistencyProofRequest, error) { - first, err := strconv.ParseInt(httpRequest.FormValue("first"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad first parameter: %v", err) - } - second, err := strconv.ParseInt(httpRequest.FormValue("second"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad second parameter: %v", err) - } - - if first < 1 { - return nil, fmt.Errorf("bad parameters: first(%d) must be a natural number", first) - } - if first >= second { - return nil, fmt.Errorf("bad parameters: second(%d) must be larger than first(%d)", first, second) - } - return &GetConsistencyProofRequest{First: first, Second: second}, nil -} - -// newGetEntryResponse assembles a log entry and its appendix -func (lp *LogParameters) newGetEntryResponse(leaf, appendix []byte) (*GetEntryResponse, error) { - return &GetEntryResponse{leaf, appendix}, nil // TODO: remove me -} - -// newGetEntriesResponse assembles a get-entries response -func (lp *LogParameters) newGetEntriesResponse(leaves []*trillian.LogLeaf) ([]*GetEntryResponse, error) { - entries := make([]*GetEntryResponse, 0, len(leaves)) - for _, leaf := range leaves { - entry, err := lp.newGetEntryResponse(leaf.GetLeafValue(), leaf.GetExtraData()) - if err != nil { - return nil, err - } - entries = append(entries, entry) - } - return entries, nil -} - -// newGetAnchorsResponse assembles a get-anchors response -func (lp *LogParameters) newGetAnchorsResponse() [][]byte { - namespaces := make([][]byte, 0, len(lp.Submitters.List())) - for _, namespace := range lp.Submitters.List() { - raw, err := namespace.Marshal() - if err != nil { - fmt.Printf("TODO: fix me and entire func\n") - continue - } - namespaces = append(namespaces, raw) - } - return namespaces -} - -// unpackJsonPost unpacks a json-encoded HTTP POST request into `unpack` -func unpackJsonPost(r *http.Request, unpack interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return fmt.Errorf("failed reading request body: %v", err) - } - if err := json.Unmarshal(body, &unpack); err != nil { - return fmt.Errorf("failed parsing json body: %v", err) - } - return nil -} - -// writeJsonBody writes a json-body HTTP response -func writeJsonResponse(response interface{}, w http.ResponseWriter) error { - json, err := json.Marshal(&response) - if err != nil { - return fmt.Errorf("json-encoding failed: %v", err) - } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(json) - if err != nil { - return fmt.Errorf("failed writing json response: %v", err) - } - return nil -} diff --git a/reqres_test.go b/reqres_test.go deleted file mode 100644 index 7c3b455..0000000 --- a/reqres_test.go +++ /dev/null @@ -1,365 +0,0 @@ -package stfe - -import ( - "bytes" - "fmt" - "strconv" - "testing" - - "net/http" - - "github.com/system-transparency/stfe/namespace/testdata" -) - -func TestNewAddCosignatureRequest(t *testing.T) { - lp := makeTestLogParameters(t, nil) - validSth := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature) - for _, table := range []struct { - description string - breq *bytes.Buffer - wantErr bool - }{ - // TODO: test cases for all errors + add wantBytes for valid cases - { - description: "invalid: unknown witness", - breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk2, testdata.Ed25519Vk2, validSth), - wantErr: true, - }, - { - description: "invalid: bad signature", - breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk, testdata.Ed25519Vk2, validSth), - wantErr: true, - }, - { - description: "valid", - breq: mustMakeAddCosiBuffer(t, testdata.Ed25519Sk, testdata.Ed25519Vk, validSth), - }, - } { - url := EndpointAddCosignature.Path("http://example.com", lp.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") - - _, err = lp.newAddCosignatureRequest(req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) - } - } -} - -// TODO: TestNewAddEntryRequest -func TestNewAddEntryRequest(t *testing.T) { -} - -func TestNewGetEntriesRequest(t *testing.T) { - lp := makeTestLogParameters(t, nil) - for _, table := range []struct { - description string - start string - end string - wantErr bool - }{ - { - description: "bad request: start must be an integer", - start: "start", - end: "10", - wantErr: true, - }, - { - description: "bad request: end must be an integer", - start: "10", - end: "end", - wantErr: true, - }, - { - description: "bad request: start must not be negative", - start: "-1", - end: "10", - wantErr: true, - }, - { - description: "bad request: start must be larger than end", - start: "1", - end: "0", - wantErr: true, - }, - { - description: "ok request but bad response: expected truncated", - start: "0", - end: fmt.Sprintf("%d", testMaxRange), - }, - { - description: "ok request and response", - start: "0", - end: "0", - }, - { - description: "ok request and response", - start: "0", - end: fmt.Sprintf("%d", testMaxRange-1), - }, - } { - url := EndpointGetEntries.Path("http://example.com/", lp.Prefix) - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must make http request in test %q: %v", table.description, err) - } - q := r.URL.Query() - q.Add("start", table.start) - q.Add("end", table.end) - r.URL.RawQuery = q.Encode() - - req, err := lp.newGetEntriesRequest(r) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error is %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - if got, want := req.Start, mustParseInt64(t, table.start); got != want { - t.Errorf("got start %d but wanted %d in test %q", got, want, table.description) - } - if got, want := req.End, min(mustParseInt64(t, table.end), req.Start+testMaxRange-1); got != want { - t.Errorf("got end %d but wanted %d in test %q", got, want, table.description) - } - } -} - -func TestNewGetProofByHashRequest(t *testing.T) { - lp := makeTestLogParameters(t, nil) - for _, table := range []struct { - description string - treeSize string - hash string - wantErr bool - }{ - { - description: "bad request: tree size must be an integer", - treeSize: "treeSize", - hash: b64(testNodeHash), - wantErr: true, - }, - { - description: "bad request: tree size must be larger than zero", - treeSize: "0", - hash: b64(testNodeHash), - wantErr: true, - }, - { - description: "bad request: hash is not base64", - treeSize: "1", - hash: "<(^_^)>", - wantErr: true, - }, - { - description: "bad request: invalid node hash (too small)", - treeSize: "1", - hash: b64(testNodeHash[1:]), - wantErr: true, - }, - { - description: "bad request: invalid node hash (too large)", - treeSize: "1", - hash: b64(append(testNodeHash, byte(0))), - wantErr: true, - }, - { - description: "ok request", - treeSize: "1", - hash: b64(testNodeHash), - }, - } { - url := EndpointGetProofByHash.Path("http://example.com/", lp.Prefix) - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must make http request in test %q: %v", table.description, err) - } - q := r.URL.Query() - q.Add("tree_size", table.treeSize) - q.Add("hash", table.hash) - r.URL.RawQuery = q.Encode() - - req, err := lp.newGetProofByHashRequest(r) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error is %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - if got, want := req.TreeSize, mustParseInt64(t, table.treeSize); got != want { - t.Errorf("got treeSize %d but wanted %d in test %q", got, want, table.description) - } - if got, want := req.Hash, mustDeb64(t, table.hash); !bytes.Equal(got, want) { - t.Errorf("got hash %X but wanted %X in test %q", got, want, table.description) - } - } -} - -func TestNewGetConsistencyProofRequest(t *testing.T) { - lp := makeTestLogParameters(t, nil) - for _, table := range []struct { - description string - first string - second string - wantErr bool - }{ - { - description: "bad reuqest: first must be an integer", - first: "first", - second: "1", - wantErr: true, - }, - { - description: "bad request: second must be an integer", - first: "1", - second: "second", - wantErr: true, - }, - { - description: "bad request: first must be larger than zero", - first: "0", - second: "2", - wantErr: true, - }, - { - description: "bad request: second must be larger than firsst", - first: "2", - second: "1", - wantErr: true, - }, - { - description: "ok request", - first: "1", - second: "2", - }, - } { - url := EndpointGetConsistencyProof.Path("http://example.com/", lp.Prefix) - r, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("must make http request in test %q: %v", table.description, err) - } - q := r.URL.Query() - q.Add("first", table.first) - q.Add("second", table.second) - r.URL.RawQuery = q.Encode() - - req, err := lp.newGetConsistencyProofRequest(r) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error is %v but wanted %v in test %q: %v", got, want, table.description, err) - } - if err != nil { - continue - } - - if got, want := req.First, mustParseInt64(t, table.first); got != want { - t.Errorf("got first %d but wanted %d in test %q", got, want, table.description) - } - if got, want := req.Second, mustParseInt64(t, table.second); got != want { - t.Errorf("got second %d but wanted %d in test %q", got, want, table.description) - } - } -} - -// TODO: refactor TestNewGetEntryResponse -func TestNewGetEntryResponse(t *testing.T) { - //lp := makeTestLogParameters(t, nil) - - //var appendix Appendix - //leaf, app := makeTestLeaf(t, testPackage, testdata.RootChain, testdata.EndEntityPrivateKey) - //if err := appendix.Unmarshal(app); err != nil { - // t.Fatalf("must unmarshal appendix: %v", err) - //} - //if _, err := lp.newGetEntryResponse(leaf, app[1:]); err == nil { - // t.Errorf("got no error invalid appendix") - //} - - //// Valid response - //rsp, err := lp.newGetEntryResponse(leaf, app) - //if err != nil { - // t.Errorf("got error %v but wanted none", err) - // return - //} - //if got, want := rsp.Item, leaf; !bytes.Equal(got, want) { - // t.Errorf("got leaf %X but wanted %X", got, want) - //} - //if got, want := rsp.Signature, appendix.Signature; !bytes.Equal(got, want) { - // t.Errorf("got signature %X but wanted %X", got, want) - //} - //if got, want := rsp.SignatureScheme, appendix.SignatureScheme; got != want { - // t.Errorf("got signature scheme %d but wanted %d", got, want) - //} - //if got, want := len(rsp.Chain), len(appendix.Chain); got != want { - // t.Errorf("got chain length %d but wanted %d", got, want) - //} - //for i, n := 0, len(rsp.Chain); i < n; i++ { - // if got, want := rsp.Chain[i], appendix.Chain[i].Data; !bytes.Equal(got, want) { - // t.Errorf("got chain[%d]=%X but wanted %X", i, got, want) - // } - //} -} - -// TODO: refactor TestNewGetEntriesResponse -func TestNewGetEntriesResponse(t *testing.T) { - //lp := makeTestLogParameters(t, nil) - - //// Invalid - //leaf := makeTrillianQueueLeafResponse(t, testPackage, testdata.RootChain, testdata.EndEntityPrivateKey, false).QueuedLeaf.Leaf - //leaf.ExtraData = leaf.ExtraData[1:] - //if _, err := lp.newGetEntriesResponse([]*trillian.LogLeaf{leaf}); err == nil { - // t.Errorf("got no error for invalid appendix") - //} - - //// Valid, including empty - //for n, numEntries := 0, 5; n < numEntries; n++ { - // leaves := make([]*trillian.LogLeaf, 0, n) - // for i := 0; i < n; i++ { - // leaves = append(leaves, makeTrillianQueueLeafResponse(t, []byte(fmt.Sprintf("%s-%d", testPackage, i)), testdata.RootChain, testdata.EndEntityPrivateKey, false).QueuedLeaf.Leaf) - // } - // if rsp, err := lp.newGetEntriesResponse(leaves); err != nil { - // t.Errorf("got error for %d valid leaves: %v", n, err) - // } else if got, want := len(rsp), n; got != want { - // t.Errorf("got %d leaves but wanted %d", got, want) - // } - // // note that we tested actual leaf contents in TestNewGetEntryResponse - //} -} - -// TODO: refactor TestNewGetAnchorsResponse -func TestNewGetAnchorsResponse(t *testing.T) { - //rawAnchors := makeTestLogParameters(t, nil).newGetAnchorsResponse() - //if got, want := len(rawAnchors), testdata.NumTrustAnchors; got != want { - // t.Errorf("got %d anchors but wanted %d", got, want) - //} - //for _, rawAnchor := range rawAnchors { - // if _, err := x509.ParseCertificate(rawAnchor); err != nil { - // t.Errorf("invalid trust anchor %X: %v", rawAnchor, err) - // } - //} -} - -func mustParseInt64(t *testing.T, num string) int64 { - n, err := strconv.ParseInt(num, 10, 64) - if err != nil { - t.Fatalf("must parse int: %v", err) - } - return n -} - -func mustDeb64(t *testing.T, str string) []byte { - b, err := deb64(str) - if err != nil { - t.Fatalf("must base64 decode: %v", err) - } - return b -} - -func min(a, b int64) int64 { - if a < b { - return a - } - return b -} diff --git a/request.go b/request.go new file mode 100644 index 0000000..5bee672 --- /dev/null +++ b/request.go @@ -0,0 +1,103 @@ +package stfe + +import ( + "fmt" + + "io/ioutil" + "net/http" + + "github.com/system-transparency/stfe/types" +) + +func (lp *LogParameters) parseAddEntryV1Request(r *http.Request) (*types.StItem, error) { + var item types.StItem + if err := unpackOctetPost(r, &item); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if item.Format != types.StFormatSignedChecksumV1 { + return nil, fmt.Errorf("invalid StItem format: %v", item.Format) + } + + // Check that submitter namespace is valid + if namespace, ok := lp.Submitters.Find(&item.SignedChecksumV1.Signature.Namespace); !ok { + return nil, fmt.Errorf("unknown namespace: %v", item.SignedChecksumV1.Signature.Namespace) + } else if msg, err := types.Marshal(item.SignedChecksumV1.Data); err != nil { + return nil, fmt.Errorf("Marshal: %v", err) // should never happen + } else if err := namespace.Verify(msg, item.SignedChecksumV1.Signature.Signature); err != nil { + return nil, fmt.Errorf("Verify: %v", err) + } + return &item, nil +} + +func (lp *LogParameters) parseAddCosignatureV1Request(r *http.Request) (*types.StItem, error) { + var item types.StItem + if err := unpackOctetPost(r, &item); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if item.Format != types.StFormatCosignedTreeHeadV1 { + return nil, fmt.Errorf("invalid StItem format: %v", item.Format) + } + + // Check that witness namespace is valid + if got, want := len(item.CosignedTreeHeadV1.Cosignatures), 1; got != want { + return nil, fmt.Errorf("invalid number of cosignatures: %d", got) + } else if namespace, ok := lp.Witnesses.Find(&item.CosignedTreeHeadV1.Cosignatures[0].Namespace); !ok { + return nil, fmt.Errorf("unknown witness: %v", item.CosignedTreeHeadV1.Cosignatures[0].Namespace) + } else if msg, err := types.Marshal(*types.NewSignedTreeHeadV1(&item.CosignedTreeHeadV1.SignedTreeHead.TreeHead, &item.CosignedTreeHeadV1.SignedTreeHead.Signature).SignedTreeHeadV1); err != nil { + return nil, fmt.Errorf("Marshal: %v", err) // should never happen + } else if err := namespace.Verify(msg, item.CosignedTreeHeadV1.Cosignatures[0].Signature); err != nil { + return nil, fmt.Errorf("Verify: %v", err) + } + return &item, nil +} + +func (lp *LogParameters) parseGetConsistencyProofV1Request(r *http.Request) (*types.GetConsistencyProofV1, error) { + var item types.GetConsistencyProofV1 + if err := unpackOctetPost(r, &item); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if item.First < 1 { + return nil, fmt.Errorf("first(%d) must be larger than zero", item.First) + } + if item.Second <= item.First { + return nil, fmt.Errorf("second(%d) must be larger than first(%d)", item.Second, item.First) + } + return &item, nil +} + +func (lp *LogParameters) parseGetProofByHashV1Request(r *http.Request) (*types.GetProofByHashV1, error) { + var item types.GetProofByHashV1 + if err := unpackOctetPost(r, &item); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + if item.TreeSize < 1 { + return nil, fmt.Errorf("TreeSize(%d) must be larger than zero", item.TreeSize) + } + return &item, nil +} + +func (lp *LogParameters) parseGetEntriesV1Request(r *http.Request) (*types.GetEntriesV1, error) { + var item types.GetEntriesV1 + if err := unpackOctetPost(r, &item); err != nil { + return nil, fmt.Errorf("unpackOctetPost: %v", err) + } + + if item.Start > item.End { + return nil, fmt.Errorf("start(%v) must be less than or equal to end(%v)", item.Start, item.End) + } + if item.End-item.Start+1 > uint64(lp.MaxRange) { + item.End = item.Start + uint64(lp.MaxRange) - 1 + } + return &item, nil +} + +func unpackOctetPost(r *http.Request, out interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return fmt.Errorf("failed reading request body: %v", err) + } + if err := types.Unmarshal(body, out); err != nil { + return fmt.Errorf("Unmarshal: %v", err) + } + return nil +} diff --git a/request_test.go b/request_test.go new file mode 100644 index 0000000..b0e5da1 --- /dev/null +++ b/request_test.go @@ -0,0 +1,318 @@ +package stfe + +import ( + "bytes" + "fmt" + "reflect" + "testing" + "testing/iotest" + + "net/http" + + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" +) + +func TestParseAddEntryV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + breq *bytes.Buffer + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + breq: bytes.NewBuffer(nil), + wantErr: true, + }, + { + description: "invalid: not a signed checksum entry", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + wantErr: true, + }, + { + description: "invalid: untrusted submitter", // only testdata.Ed25519VkSubmitter is registered by default in newLogParameters() + + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter2), + wantErr: true, + }, + { + description: "invalid: signature does not cover message", + + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter2, testdata.Ed25519VkSubmitter), + wantErr: true, + }, + { + description: "valid", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + }, + } { + url := EndpointAddEntry.Path("http://example.com", lp.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/octet-stream") + + _, err = lp.parseAddEntryV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestParseAddCosignatureV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + breq *bytes.Buffer + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + breq: bytes.NewBuffer(nil), + wantErr: true, + }, + { + description: "invalid: not a cosigned sth", + breq: testdata.AddSignedChecksumBuffer(t, testdata.Ed25519SkSubmitter, testdata.Ed25519VkSubmitter), + wantErr: true, + }, + { + description: "invalid: no cosignature", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, nil), + wantErr: true, + }, + { + description: "invalid: untrusted witness", // only testdata.Ed25519VkWitness is registered by default in newLogParameters() + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness2), + wantErr: true, + }, + { + description: "invalid: signature does not cover message", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness2, &testdata.Ed25519VkWitness), + wantErr: true, + }, + { + description: "valid", + breq: testdata.AddCosignatureBuffer(t, testdata.DefaultSth(t, testdata.Ed25519VkLog), &testdata.Ed25519SkWitness, &testdata.Ed25519VkWitness), + }, + } { + url := EndpointAddCosignature.Path("http://example.com", lp.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/octet-stream") + + _, err = lp.parseAddCosignatureV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestNewGetConsistencyProofRequest(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetConsistencyProofV1 + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: first must be larger than zero", + req: &types.GetConsistencyProofV1{First: 0, Second: 0}, + wantErr: true, + }, + { + description: "invalid: second must be larger than first", + req: &types.GetConsistencyProofV1{First: 2, Second: 1}, + wantErr: true, + }, + { + description: "valid", + req: &types.GetConsistencyProofV1{First: 1, Second: 2}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetConsistencyProof.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseGetConsistencyProofV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestNewGetProofByHashRequest(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetProofByHashV1 + wantErr bool + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: no entry in an empty tree", + req: &types.GetProofByHashV1{TreeSize: 0, Hash: testdata.LeafHash}, + wantErr: true, + }, + { + description: "valid", + req: &types.GetProofByHashV1{TreeSize: 1, Hash: testdata.LeafHash}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetProofByHash.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + _, err = lp.parseGetProofByHashV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + } +} + +func TestParseGetEntriesV1Request(t *testing.T) { + lp := newLogParameters(t, nil) + for _, table := range []struct { + description string + req *types.GetEntriesV1 + wantErr bool + wantReq *types.GetEntriesV1 + }{ + { + description: "invalid: nothing to unpack", + req: nil, + wantErr: true, + }, + { + description: "invalid: start must be larger than end", + req: &types.GetEntriesV1{Start: 1, End: 0}, + wantErr: true, + }, + { + description: "valid: want truncated range", + req: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange)}, + wantReq: &types.GetEntriesV1{Start: 0, End: uint64(testdata.MaxRange) - 1}, + }, + { + description: "valid", + req: &types.GetEntriesV1{Start: 0, End: 0}, + wantReq: &types.GetEntriesV1{Start: 0, End: 0}, + }, + } { + var buf *bytes.Buffer + if table.req == nil { + buf = bytes.NewBuffer(nil) + } else { + buf = bytes.NewBuffer(marshal(t, *table.req)) + } + + url := EndpointGetEntries.Path("http://example.com", lp.Prefix) + req, err := http.NewRequest("POST", url, buf) + if err != nil { + t.Fatalf("failed creating http request: %v", err) + } + req.Header.Set("Content-Type", "application/octet-stream") + + output, err := lp.parseGetEntriesV1Request(req) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got errror %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + continue + } + if got, want := output, table.wantReq; !reflect.DeepEqual(got, want) { + t.Errorf("got request\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) + } + } +} + +func TestUnpackOctetPost(t *testing.T) { + for _, table := range []struct { + description string + req *http.Request + out interface{} + wantErr bool + }{ + { + description: "invalid: cannot read request body", + req: func() *http.Request { + req, err := http.NewRequest(http.MethodPost, "", iotest.ErrReader(fmt.Errorf("bad reader"))) + if err != nil { + t.Fatalf("must make new http request: %v", err) + } + return req + }(), + out: &types.StItem{}, + wantErr: true, + }, + { + description: "invalid: cannot unmarshal", + req: func() *http.Request { + req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer(nil)) + if err != nil { + t.Fatalf("must make new http request: %v", err) + } + return req + }(), + out: &types.StItem{}, + wantErr: true, + }, + { + description: "valid", + req: func() *http.Request { + req, err := http.NewRequest(http.MethodPost, "", bytes.NewBuffer([]byte{0})) + if err != nil { + t.Fatalf("must make new http request: %v", err) + } + return req + }(), + out: &struct{ SomeUint8 uint8 }{}, + }, + } { + err := unpackOctetPost(table.req, table.out) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) + } + } +} + +func marshal(t *testing.T, out interface{}) []byte { + b, err := types.Marshal(out) + if err != nil { + t.Fatalf("must marshal: %v", err) + } + return b +} diff --git a/server/main.go b/server/main.go index 7402fb3..a00758e 100644 --- a/server/main.go +++ b/server/main.go @@ -20,7 +20,7 @@ import ( "github.com/google/trillian" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/system-transparency/stfe" - "github.com/system-transparency/stfe/namespace" + "github.com/system-transparency/stfe/types" "google.golang.org/grpc" ) @@ -31,8 +31,8 @@ var ( trillianID = flag.Int64("trillian_id", 5991359069696313945, "log identifier in the Trillian database") deadline = flag.Duration("deadline", time.Second*10, "deadline for backend requests") key = flag.String("key", "8gzezwrU/2eTrO6tEYyLKsoqn5V54URvKIL9cTE7jUYUqXVX4neJvcBq/zpSAYPsZFG1woh0OGBzQbi9UP9MZw==", "base64-encoded Ed25519 signing key") - submitters = flag.String("submitters", "AAEgHOQFUkKNWpjYAhNKTyWCzahlI7RDtf5123kHD2LACj0=,AAEgLqrWb9JwQUTk/SwTNDdMH8aRmy3mbmhwEepO5WSgb+A=", "comma-separated list of trusted submitter namespaces in base64 (default: testdata.Ed25519{Vk,Vk2})") - witnesses = flag.String("witnesses", "", "comma-separated list of trusted submitter namespaces in base64 (default: none") + submitters = flag.String("submitters", "AAEgHOQFUkKNWpjYAhNKTyWCzahlI7RDtf5123kHD2LACj0=,AAEgLqrWb9JwQUTk/SwTNDdMH8aRmy3mbmhwEepO5WSgb+A=", "comma-separated list of trusted submitter namespaces in base64 (default: testdata.Ed25519{Vk,Vk2})") // TODO: update to valid submitter namespaces + witnesses = flag.String("witnesses", "", "comma-separated list of trusted submitter namespaces in base64 (default: none") // TODO: update to valid witness namespaces maxRange = flag.Int64("max_range", 2, "maximum number of entries that can be retrived in a single request") interval = flag.Duration("interval", time.Second*30, "interval used to rotate the log's cosigned STH") ) @@ -113,7 +113,7 @@ func setupInstanceFromFlags() (*stfe.Instance, error) { return nil, fmt.Errorf("sk: DecodeString: %v", err) } signer := ed25519.PrivateKey(sk) - logId, err := namespace.NewNamespaceEd25519V1([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))) + logId, err := types.NewNamespaceEd25519V1([]byte(ed25519.PrivateKey(sk).Public().(ed25519.PublicKey))) if err != nil { return nil, fmt.Errorf("NewNamespaceEd25519V1: %v", err) } @@ -128,7 +128,7 @@ func setupInstanceFromFlags() (*stfe.Instance, error) { return nil, fmt.Errorf("NewActiveSthSource: %v", err) } // Setup log instance - i := stfe.NewInstance(lp, client, source) + i := &stfe.Instance{client, lp, source} for _, handler := range i.Handlers() { glog.V(3).Infof("adding handler: %s", handler.Path()) mux.Handle(handler.Path(), handler) @@ -138,22 +138,22 @@ func setupInstanceFromFlags() (*stfe.Instance, error) { // newNamespacePoolFromString creates a new namespace pool from a // comma-separated list of serialized and base64-encoded namespaces. -func newNamespacePoolFromString(str string) (*namespace.NamespacePool, error) { - var namespaces []*namespace.Namespace +func newNamespacePoolFromString(str string) (*types.NamespacePool, error) { + var namespaces []*types.Namespace if len(str) > 0 { for _, b64 := range strings.Split(str, ",") { b, err := base64.StdEncoding.DecodeString(b64) if err != nil { return nil, fmt.Errorf("DecodeString: %v", err) } - var namespace namespace.Namespace - if err := namespace.Unmarshal(b); err != nil { + var namespace types.Namespace + if err := types.Unmarshal(b, &namespace); err != nil { return nil, fmt.Errorf("Unmarshal: %v", err) } namespaces = append(namespaces, &namespace) } } - pool, err := namespace.NewNamespacePool(namespaces) + pool, err := types.NewNamespacePool(namespaces) if err != nil { return nil, fmt.Errorf("NewNamespacePool: %v", err) } diff --git a/sth.go b/sth.go index 58b5c35..30ba965 100644 --- a/sth.go +++ b/sth.go @@ -9,21 +9,22 @@ import ( "github.com/golang/glog" "github.com/google/certificate-transparency-go/schedule" "github.com/google/trillian" - "github.com/google/trillian/types" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/types" ) // SthSource provides access to the log's STHs. type SthSource interface { // Latest returns the most reccent signed_tree_head_v*. - Latest(context.Context) (*StItem, error) + Latest(context.Context) (*types.StItem, error) // Stable returns the most recent signed_tree_head_v* that is stable for // some period of time, e.g., 10 minutes. - Stable(context.Context) (*StItem, error) + Stable(context.Context) (*types.StItem, error) // Cosigned returns the most recent cosigned_tree_head_v*. - Cosigned(context.Context) (*StItem, error) + Cosigned(context.Context) (*types.StItem, error) // AddCosignature attempts to add a cosignature to the stable STH. The // passed cosigned_tree_head_v* must have a single verified cosignature. - AddCosignature(context.Context, *StItem) error + AddCosignature(context.Context, *types.StItem) error // Run keeps the STH source updated until cancelled Run(context.Context) } @@ -33,9 +34,9 @@ type SthSource interface { type ActiveSthSource struct { client trillian.TrillianLogClient logParameters *LogParameters - currCosth *StItem // current cosigned_tree_head_v1 (already finalized) - nextCosth *StItem // next cosigned_tree_head_v1 (under preparation) - cosignatureFrom map[string]bool // track who we got cosignatures from in nextCosth + currCosth *types.StItem // current cosigned_tree_head_v1 (already finalized) + nextCosth *types.StItem // next cosigned_tree_head_v1 (under preparation) + cosignatureFrom map[[types.NamespaceFingerprintSize]byte]bool // track who we got cosignatures from in nextCosth mutex sync.RWMutex } @@ -51,10 +52,11 @@ func NewActiveSthSource(cli trillian.TrillianLogClient, lp *LogParameters) (*Act if err != nil { return nil, fmt.Errorf("Latest: %v", err) } + // TODO: load persisted cosigned STH? - s.currCosth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) - s.nextCosth = NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) - s.cosignatureFrom = make(map[string]bool) + s.currCosth = types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) + s.nextCosth = types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil) + s.cosignatureFrom = make(map[[types.NamespaceFingerprintSize]byte]bool) return &s, nil } @@ -70,78 +72,84 @@ func (s *ActiveSthSource) Run(ctx context.Context) { // rotate s.mutex.Lock() defer s.mutex.Unlock() - s.rotate(sth) + if err := s.rotate(sth); err != nil { + glog.Warningf("rotate failed: %v", err) + } // TODO: persist cosigned STH? }) } -func (s *ActiveSthSource) Latest(ctx context.Context) (*StItem, error) { +func (s *ActiveSthSource) Latest(ctx context.Context) (*types.StItem, error) { trsp, err := s.client.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ LogId: s.logParameters.TreeId, }) - var lr types.LogRootV1 + var lr ttypes.LogRootV1 if errInner := checkGetLatestSignedLogRoot(s.logParameters, trsp, err, &lr); errInner != nil { return nil, fmt.Errorf("invalid signed log root response: %v", errInner) } - return s.logParameters.genV1Sth(NewTreeHeadV1(&lr)) + return s.logParameters.SignTreeHeadV1(NewTreeHeadV1FromLogRoot(&lr)) } -func (s *ActiveSthSource) Stable(_ context.Context) (*StItem, error) { +func (s *ActiveSthSource) Stable(_ context.Context) (*types.StItem, error) { s.mutex.RLock() defer s.mutex.RUnlock() if s.nextCosth == nil { return nil, fmt.Errorf("no stable sth available") } - return &StItem{ - Format: StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &s.nextCosth.CosignedTreeHeadV1.SignedTreeHeadV1, - }, nil + return types.NewSignedTreeHeadV1(&s.nextCosth.CosignedTreeHeadV1.SignedTreeHead.TreeHead, &s.nextCosth.CosignedTreeHeadV1.SignedTreeHead.Signature), nil } -func (s *ActiveSthSource) Cosigned(_ context.Context) (*StItem, error) { +func (s *ActiveSthSource) Cosigned(_ context.Context) (*types.StItem, error) { s.mutex.RLock() defer s.mutex.RUnlock() - if s.currCosth == nil || len(s.currCosth.CosignedTreeHeadV1.SignatureV1) == 0 { + if s.currCosth == nil || len(s.currCosth.CosignedTreeHeadV1.Cosignatures) == 0 { return nil, fmt.Errorf("no cosigned sth available") } return s.currCosth, nil } -func (s *ActiveSthSource) AddCosignature(_ context.Context, costh *StItem) error { +func (s *ActiveSthSource) AddCosignature(_ context.Context, costh *types.StItem) error { s.mutex.Lock() defer s.mutex.Unlock() - if !reflect.DeepEqual(s.nextCosth.CosignedTreeHeadV1.SignedTreeHeadV1, costh.CosignedTreeHeadV1.SignedTreeHeadV1) { + if !reflect.DeepEqual(s.nextCosth.CosignedTreeHeadV1.SignedTreeHead, costh.CosignedTreeHeadV1.SignedTreeHead) { return fmt.Errorf("cosignature covers a different tree head") } - witness := costh.CosignedTreeHeadV1.SignatureV1[0].Namespace.String() - if _, ok := s.cosignatureFrom[witness]; ok { + witness, err := costh.CosignedTreeHeadV1.Cosignatures[0].Namespace.Fingerprint() + if err != nil { + return fmt.Errorf("namespace without fingerprint: %v", err) + } + if _, ok := s.cosignatureFrom[*witness]; ok { return nil // duplicate } - s.cosignatureFrom[witness] = true - s.nextCosth.CosignedTreeHeadV1.SignatureV1 = append(s.nextCosth.CosignedTreeHeadV1.SignatureV1, costh.CosignedTreeHeadV1.SignatureV1[0]) + s.cosignatureFrom[*witness] = true + s.nextCosth.CosignedTreeHeadV1.Cosignatures = append(s.nextCosth.CosignedTreeHeadV1.Cosignatures, costh.CosignedTreeHeadV1.Cosignatures[0]) 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 (s *ActiveSthSource) rotate(fixedSth *StItem) { +func (s *ActiveSthSource) rotate(fixedSth *types.StItem) error { // rotate stable -> cosigned - if reflect.DeepEqual(&s.currCosth.CosignedTreeHeadV1.SignedTreeHeadV1, &s.nextCosth.CosignedTreeHeadV1.SignedTreeHeadV1) { - for _, sigv1 := range s.currCosth.CosignedTreeHeadV1.SignatureV1 { - witness := sigv1.Namespace.String() - if _, ok := s.cosignatureFrom[witness]; !ok { - s.cosignatureFrom[witness] = true - s.nextCosth.CosignedTreeHeadV1.SignatureV1 = append(s.nextCosth.CosignedTreeHeadV1.SignatureV1, sigv1) + if reflect.DeepEqual(&s.currCosth.CosignedTreeHeadV1.SignedTreeHead, &s.nextCosth.CosignedTreeHeadV1.SignedTreeHead) { + for _, sigv1 := range s.currCosth.CosignedTreeHeadV1.Cosignatures { + witness, err := sigv1.Namespace.Fingerprint() + if err != nil { + return fmt.Errorf("namespace without fingerprint: %v", err) + } + if _, ok := s.cosignatureFrom[*witness]; !ok { + s.cosignatureFrom[*witness] = true + s.nextCosth.CosignedTreeHeadV1.Cosignatures = append(s.nextCosth.CosignedTreeHeadV1.Cosignatures, sigv1) } } } - s.currCosth.CosignedTreeHeadV1.SignedTreeHeadV1 = s.nextCosth.CosignedTreeHeadV1.SignedTreeHeadV1 - s.currCosth.CosignedTreeHeadV1.SignatureV1 = make([]SignatureV1, len(s.nextCosth.CosignedTreeHeadV1.SignatureV1)) - copy(s.currCosth.CosignedTreeHeadV1.SignatureV1, s.nextCosth.CosignedTreeHeadV1.SignatureV1) + s.currCosth.CosignedTreeHeadV1.SignedTreeHead = s.nextCosth.CosignedTreeHeadV1.SignedTreeHead + s.currCosth.CosignedTreeHeadV1.Cosignatures = make([]types.SignatureV1, len(s.nextCosth.CosignedTreeHeadV1.Cosignatures)) + copy(s.currCosth.CosignedTreeHeadV1.Cosignatures, s.nextCosth.CosignedTreeHeadV1.Cosignatures) // rotate new stable -> stable - if !reflect.DeepEqual(&s.nextCosth.CosignedTreeHeadV1.SignedTreeHeadV1, fixedSth.SignedTreeHeadV1) { - s.nextCosth = NewCosignedTreeHeadV1(fixedSth.SignedTreeHeadV1, nil) - s.cosignatureFrom = make(map[string]bool) + if !reflect.DeepEqual(&s.nextCosth.CosignedTreeHeadV1.SignedTreeHead, fixedSth.SignedTreeHeadV1) { + s.nextCosth = types.NewCosignedTreeHeadV1(fixedSth.SignedTreeHeadV1, nil) + s.cosignatureFrom = make(map[[types.NamespaceFingerprintSize]byte]bool) } + return nil } diff --git a/sth_test.go b/sth_test.go index e3350dd..e5914c6 100644 --- a/sth_test.go +++ b/sth_test.go @@ -10,8 +10,8 @@ import ( "github.com/golang/mock/gomock" cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/google/trillian" - "github.com/system-transparency/stfe/namespace" - "github.com/system-transparency/stfe/namespace/testdata" + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" ) func TestNewActiveSthSource(t *testing.T) { @@ -21,28 +21,28 @@ func TestNewActiveSthSource(t *testing.T) { trsp *trillian.GetLatestSignedLogRootResponse terr error wantErr bool - wantCosi *StItem // current cosigned sth - wantStable *StItem // next stable sth that signatures are collected for + wantCosi *types.StItem // current cosigned sth + wantStable *types.StItem // next stable sth that signatures are collected for }{ { - description: "invalid trillian response", - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), + description: "invalid: no Trillian response", + signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), terr: fmt.Errorf("internal server error"), wantErr: true, }, { description: "valid", - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), - trsp: makeLatestSignedLogRootResponse(t, testTimestamp, testTreeSize, testNodeHash), - wantCosi: NewCosignedTreeHeadV1(NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature).SignedTreeHeadV1, nil), - wantStable: NewCosignedTreeHeadV1(NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature).SignedTreeHeadV1, nil), + signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), + trsp: testdata.DefaultTSlr(t), + wantCosi: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + wantStable: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), }, } { func() { // run deferred functions at the end of each iteration - th := newTestHandler(t, table.signer, nil) - defer th.mockCtrl.Finish() - th.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) - source, err := NewActiveSthSource(th.client, th.instance.LogParameters) + ti := newTestInstance(t, table.signer) + defer ti.ctrl.Finish() + ti.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) + source, err := NewActiveSthSource(ti.client, ti.instance.LogParameters) 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) } @@ -51,20 +51,20 @@ func TestNewActiveSthSource(t *testing.T) { } if got, want := source.currCosth, table.wantCosi; !reflect.DeepEqual(got, want) { - t.Errorf("got cosigned sth %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got cosigned sth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) } if got, want := source.nextCosth, table.wantStable; !reflect.DeepEqual(got, want) { - t.Errorf("got stable sth %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got stable sth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) } - cosignatureFrom := make(map[string]bool) - for _, wit := range table.wantStable.CosignedTreeHeadV1.SignatureV1 { - cosignatureFrom[wit.Namespace.String()] = true + cosignatureFrom := make(map[[types.NamespaceFingerprintSize]byte]bool) + for _, cosig := range table.wantStable.CosignedTreeHeadV1.Cosignatures { + cosignatureFrom[testdata.Fingerprint(t, &cosig.Namespace)] = true } if got, want := source.cosignatureFrom, cosignatureFrom; !reflect.DeepEqual(got, want) { if got == nil { - t.Errorf("got uninitialized witness map %v but wanted %v in test %q", nil, want, table.description) + t.Errorf("got uninitialized witness map\n%v\n\tbut wanted\n%v\n\tin test %q", nil, want, table.description) } else { - t.Errorf("got witness map %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got witness map\n%v\n\t but wanted\n%v\n\tin test %q", got, want, table.description) } } }() @@ -78,32 +78,32 @@ func TestLatest(t *testing.T) { trsp *trillian.GetLatestSignedLogRootResponse terr error wantErr bool - wantRsp *StItem + wantRsp *types.StItem }{ { - description: "invalid trillian response", - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), + description: "invalid: no Trillian response", + signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), terr: fmt.Errorf("internal server error"), wantErr: true, }, { - description: "signature failure", + description: "invalid: no signature", signer: cttestdata.NewSignerWithErr(nil, fmt.Errorf("signing failed")), terr: fmt.Errorf("internal server error"), wantErr: true, }, { description: "valid", - signer: cttestdata.NewSignerWithFixedSig(nil, testSignature), - trsp: makeLatestSignedLogRootResponse(t, testTimestamp, testTreeSize, testNodeHash), - wantRsp: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature), + signer: cttestdata.NewSignerWithFixedSig(nil, testdata.Signature), + trsp: testdata.DefaultTSlr(t), + wantRsp: testdata.DefaultSth(t, testdata.Ed25519VkLog), }, } { func() { // run deferred functions at the end of each iteration - th := newTestHandler(t, table.signer, nil) - defer th.mockCtrl.Finish() - th.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) - sth, err := th.instance.SthSource.Latest(context.Background()) + ti := newTestInstance(t, table.signer) + defer ti.ctrl.Finish() + ti.client.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.trsp, table.terr) + sth, err := ti.instance.SthSource.Latest(context.Background()) if got, want := err != nil, table.wantErr; got != want { t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) } @@ -111,30 +111,30 @@ func TestLatest(t *testing.T) { return } if got, want := sth, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got\n%v\n\tbut wanted\n%v\n\t in test %q", got, want, table.description) } }() } } func TestStable(t *testing.T) { - sth := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature) for _, table := range []struct { description string source SthSource - wantRsp *StItem + wantRsp *types.StItem wantErr bool }{ { - description: "no stable sth", + description: "invalid: no stable sth", source: &ActiveSthSource{}, wantErr: true, }, { description: "valid", source: &ActiveSthSource{ - nextCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil)}, - wantRsp: sth, + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + }, + wantRsp: testdata.DefaultSth(t, testdata.Ed25519VkLog), }, } { sth, err := table.source.Stable(context.Background()) @@ -145,43 +145,36 @@ func TestStable(t *testing.T) { continue } if got, want := sth, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got\n%v\n\t but wanted\n%v\n\t in test %q", got, want, table.description) } } } func TestCosigned(t *testing.T) { - sth := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature) - sigs := []SignatureV1{ - SignatureV1{ - Namespace: *mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - Signature: testSignature, - }, - } for _, table := range []struct { description string source SthSource - wantRsp *StItem + wantRsp *types.StItem wantErr bool }{ { - description: "no cosigned sth: nil", + description: "invalid: no cosigned sth: nil", source: &ActiveSthSource{}, wantErr: true, }, { - description: "no cosigned sth: nil signatures", + description: "invalid: no cosigned sth: nil signatures", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil), + currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), }, wantErr: true, }, { description: "valid", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, sigs), + currCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), }, - wantRsp: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, sigs), + wantRsp: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), }, } { cosi, err := table.source.Cosigned(context.Background()) @@ -192,91 +185,58 @@ func TestCosigned(t *testing.T) { continue } if got, want := cosi, table.wantRsp; !reflect.DeepEqual(got, want) { - t.Errorf("got %v but wanted %v in test %q", got, want, table.description) + t.Errorf("got\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) } } } func TestAddCosignature(t *testing.T) { - sth := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature) - wit1 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk) - wit2 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk2) for _, table := range []struct { description string source *ActiveSthSource - req *StItem - wantWit []*namespace.Namespace + req *types.StItem + wantWit []*types.Namespace wantErr bool }{ { description: "invalid: cosignature must target the stable sth", source: &ActiveSthSource{ - nextCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil), - cosignatureFrom: make(map[string]bool), + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), }, - req: NewCosignedTreeHeadV1(NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp+1000000, testTreeSize, testNodeHash)), testLogId, testSignature).SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit1, - Signature: testSignature, - }, - }), + req: testdata.DefaultCosth(t, testdata.Ed25519VkLog2, [][32]byte{testdata.Ed25519VkWitness}), wantErr: true, }, { description: "valid: adding duplicate into a pool of cosignatures", source: &ActiveSthSource{ - nextCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit1, - Signature: testSignature, - }, - }), - cosignatureFrom: map[string]bool{ - wit1.String(): true, + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, testdata.NewNamespace(t, testdata.Ed25519VkWitness)): true, }, }, - req: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit1, - Signature: testSignature, - }, - }), - wantWit: []*namespace.Namespace{wit1}, + req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness)}, }, { description: "valid: adding into an empty pool of cosignatures", source: &ActiveSthSource{ - nextCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, nil), - cosignatureFrom: make(map[string]bool), + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, nil), + cosignatureFrom: make(map[[types.NamespaceFingerprintSize]byte]bool), }, - req: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit1, - Signature: testSignature, - }, - }), - wantWit: []*namespace.Namespace{wit1}, + req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness)}, }, { description: "valid: adding into a pool of cosignatures", source: &ActiveSthSource{ - nextCosth: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit1, - Signature: testSignature, - }, - }), - cosignatureFrom: map[string]bool{ - wit1.String(): true, + nextCosth: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness}), + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, testdata.NewNamespace(t, testdata.Ed25519VkWitness)): true, }, }, - req: NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ - Namespace: *wit2, - Signature: testSignature, - }, - }), - wantWit: []*namespace.Namespace{wit1, wit2}, + req: testdata.DefaultCosth(t, testdata.Ed25519VkLog, [][32]byte{testdata.Ed25519VkWitness2}), + wantWit: []*types.Namespace{testdata.NewNamespace(t, testdata.Ed25519VkWitness), testdata.NewNamespace(t, testdata.Ed25519VkWitness2)}, }, } { err := table.source.AddCosignature(context.Background(), table.req) @@ -288,23 +248,23 @@ func TestAddCosignature(t *testing.T) { } // Check that the next cosigned sth is updated - var sigs []SignatureV1 + var sigs []types.SignatureV1 for _, wit := range table.wantWit { - sigs = append(sigs, SignatureV1{ + sigs = append(sigs, types.SignatureV1{ Namespace: *wit, - Signature: testSignature, + Signature: testdata.Signature, }) } - if got, want := table.source.nextCosth, NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, sigs); !reflect.DeepEqual(got, want) { - t.Errorf("got %v but wanted %v in test %q", got, want, table.description) + if got, want := table.source.nextCosth, types.NewCosignedTreeHeadV1(testdata.DefaultSth(t, testdata.Ed25519VkLog).SignedTreeHeadV1, sigs); !reflect.DeepEqual(got, want) { + t.Errorf("got\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) } // Check that the map tracking witness signatures is updated if got, want := len(table.source.cosignatureFrom), len(table.wantWit); got != want { t.Errorf("witness map got %d cosignatures but wanted %d in test %q", got, want, table.description) } else { for _, wit := range table.wantWit { - if _, ok := table.source.cosignatureFrom[wit.String()]; !ok { - t.Errorf("missing signature from witness %X in test %q", wit.String(), table.description) + if _, ok := table.source.cosignatureFrom[testdata.Fingerprint(t, wit)]; !ok { + t.Errorf("missing signature from witness %X in test %q", testdata.Fingerprint(t, wit), table.description) } } } @@ -312,199 +272,201 @@ func TestAddCosignature(t *testing.T) { } func TestRotate(t *testing.T) { - sth1 := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature) - sth2 := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp+1000000, testTreeSize+1, testNodeHash)), testLogId, testSignature) - sth3 := NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp+2000000, testTreeSize+2, testNodeHash)), testLogId, testSignature) - wit1 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk) - wit2 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk2) - wit3 := mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk3) + // distinct sths + sth1 := testdata.DefaultSth(t, testdata.Ed25519VkLog) + sth2 := testdata.DefaultSth(t, testdata.Ed25519VkLog2) + sth3 := testdata.DefaultSth(t, testdata.Ed25519VkLog3) + // distinct witnesses + wit1 := testdata.NewNamespace(t, testdata.Ed25519VkWitness) + wit2 := testdata.NewNamespace(t, testdata.Ed25519VkWitness2) + wit3 := testdata.NewNamespace(t, testdata.Ed25519VkWitness3) for _, table := range []struct { description string source *ActiveSthSource - fixedSth *StItem - wantCurrSth *StItem - wantNextSth *StItem - wantWit []*namespace.Namespace + fixedSth *types.StItem + wantCurrSth *types.StItem + wantNextSth *types.StItem + wantWit []*types.Namespace }{ { description: "not repeated cosigned and not repeated stable", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), - nextCosth: NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), + nextCosth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - cosignatureFrom: map[string]bool{ - wit1.String(): true, + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, wit1): true, }, }, fixedSth: sth3, - wantCurrSth: NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantCurrSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantNextSth: NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), + wantNextSth: types.NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), wantWit: nil, // no cosignatures for the next stable sth yet }, { description: "not repeated cosigned and repeated stable", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), - nextCosth: NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, nil), + nextCosth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - cosignatureFrom: map[string]bool{ - wit1.String(): true, + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, wit1): true, }, }, fixedSth: sth2, - wantCurrSth: NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantCurrSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantNextSth: NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantNextSth: types.NewCosignedTreeHeadV1(sth2.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantWit: []*namespace.Namespace{wit1}, + wantWit: []*types.Namespace{wit1}, }, { description: "repeated cosigned and not repeated stable", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, }), - nextCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + nextCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit3, - Signature: testSignature, + Signature: testdata.Signature, }, }), - cosignatureFrom: map[string]bool{ - wit2.String(): true, - wit3.String(): true, + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, wit2): true, + testdata.Fingerprint(t, wit3): true, }, }, fixedSth: sth3, - wantCurrSth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantCurrSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit3, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantNextSth: NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), + wantNextSth: types.NewCosignedTreeHeadV1(sth3.SignedTreeHeadV1, nil), wantWit: nil, // no cosignatures for the next stable sth yet }, { description: "repeated cosigned and repeated stable", source: &ActiveSthSource{ - currCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + currCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, }), - nextCosth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + nextCosth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit3, - Signature: testSignature, + Signature: testdata.Signature, }, }), - cosignatureFrom: map[string]bool{ - wit2.String(): true, - wit3.String(): true, + cosignatureFrom: map[[types.NamespaceFingerprintSize]byte]bool{ + testdata.Fingerprint(t, wit2): true, + testdata.Fingerprint(t, wit3): true, }, }, fixedSth: sth1, - wantCurrSth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantCurrSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit3, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantNextSth: NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []SignatureV1{ - SignatureV1{ + wantNextSth: types.NewCosignedTreeHeadV1(sth1.SignedTreeHeadV1, []types.SignatureV1{ + types.SignatureV1{ Namespace: *wit2, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit3, - Signature: testSignature, + Signature: testdata.Signature, }, - SignatureV1{ + types.SignatureV1{ Namespace: *wit1, - Signature: testSignature, + Signature: testdata.Signature, }, }), - wantWit: []*namespace.Namespace{wit1, wit2, wit3}, + wantWit: []*types.Namespace{wit1, wit2, wit3}, }, } { table.source.rotate(table.fixedSth) if got, want := table.source.currCosth, table.wantCurrSth; !reflect.DeepEqual(got, want) { - t.Errorf("got currCosth %X but wanted %X in test %q", got, want, table.description) + t.Errorf("got currCosth\n%v\n\tbut wanted \n%v\n\tin test %q", got, want, table.description) } if got, want := table.source.nextCosth, table.wantNextSth; !reflect.DeepEqual(got, want) { - t.Errorf("got nextCosth %X but wanted %X in test %q", got, want, table.description) + t.Errorf("got nextCosth\n%v\n\tbut wanted\n%v\n\tin test %q", got, want, table.description) } if got, want := len(table.source.cosignatureFrom), len(table.wantWit); got != want { t.Errorf("witness map got %d cosignatures but wanted %d in test %q", got, want, table.description) } else { for _, wit := range table.wantWit { - if _, ok := table.source.cosignatureFrom[wit.String()]; !ok { - t.Errorf("missing signature from witness %X in test %q", wit.String(), table.description) + if _, ok := table.source.cosignatureFrom[testdata.Fingerprint(t, wit)]; !ok { + t.Errorf("missing signature from witness %X in test %q", testdata.Fingerprint(t, wit), table.description) } } } // check that adding cosignatures to stable will not effect cosigned sth - wantLen := len(table.source.currCosth.CosignedTreeHeadV1.SignatureV1) - table.source.nextCosth.CosignedTreeHeadV1.SignatureV1 = append(table.source.nextCosth.CosignedTreeHeadV1.SignatureV1, SignatureV1{Namespace: *wit1, Signature: testSignature}) - if gotLen := len(table.source.currCosth.CosignedTreeHeadV1.SignatureV1); gotLen != wantLen { + wantLen := len(table.source.currCosth.CosignedTreeHeadV1.Cosignatures) + table.source.nextCosth.CosignedTreeHeadV1.Cosignatures = append(table.source.nextCosth.CosignedTreeHeadV1.Cosignatures, types.SignatureV1{Namespace: *wit1, Signature: testdata.Signature}) + if gotLen := len(table.source.currCosth.CosignedTreeHeadV1.Cosignatures); gotLen != wantLen { t.Errorf("adding cosignatures to the stable sth modifies the fixated cosigned sth in test %q", table.description) } } diff --git a/testdata/data.go b/testdata/data.go new file mode 100644 index 0000000..ac958e5 --- /dev/null +++ b/testdata/data.go @@ -0,0 +1,287 @@ +package testdata + +import ( + "bytes" + "testing" + "time" + + "crypto/ed25519" + + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + Ed25519VkLog = [32]byte{} + Ed25519VkLog2 = [32]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + Ed25519VkLog3 = [32]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} + //Ed25519VkWitness = [32]byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3} + // Ed25519VkWitness2 = [32]byte{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4} + Ed25519VkWitness3 = [32]byte{5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5} + //Ed25519VkSubmitter = [32]byte{6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6} + + TreeId = int64(0) + Prefix = "test" + MaxRange = int64(3) + Interval = time.Second * 10 + Deadline = time.Second * 5 + + Timestamp = uint64(0) + TreeSize = uint64(0) + Extension = make([]byte, 0) + NodeHash = make([]byte, 32) + Signature = make([]byte, 64) + Identifier = []byte("foobar-1.2.3") + Checksum = make([]byte, 32) + Index = int64(0) + HashPath = [][]byte{ + NodeHash, + } + NodePath = []types.NodeHash{ + types.NodeHash{NodeHash}, + } + LeafHash = [32]byte{} + + // TODO: make these unique and load more pretty maybe + Ed25519SkWitness = [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} + Ed25519VkWitness = [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} + + Ed25519SkWitness2 = [64]byte{98, 65, 92, 117, 33, 167, 138, 36, 252, 147, 87, 173, 44, 62, 17, 66, 126, 70, 218, 87, 91, 148, 64, 194, 241, 248, 62, 90, 140, 122, 234, 76, 144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} + Ed25519VkWitness2 = [32]byte{144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} + + Ed25519SkSubmitter = [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} + Ed25519VkSubmitter = [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} + Ed25519SkSubmitter2 = [64]byte{98, 65, 92, 117, 33, 167, 138, 36, 252, 147, 87, 173, 44, 62, 17, 66, 126, 70, 218, 87, 91, 148, 64, 194, 241, 248, 62, 90, 140, 122, 234, 76, 144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} + Ed25519VkSubmitter2 = [32]byte{144, 6, 250, 185, 37, 217, 77, 201, 180, 42, 81, 37, 165, 27, 22, 32, 25, 8, 156, 228, 78, 207, 208, 18, 91, 77, 189, 51, 112, 31, 237, 6} +) + +// TODO: reorder and docdoc where need be +// +// Helpers that must create default values for different STFE types +// + +func DefaultCosth(t *testing.T, logVk [32]byte, witVk [][32]byte) *types.StItem { + t.Helper() + cosigs := make([]types.SignatureV1, 0) + for _, vk := range witVk { + cosigs = append(cosigs, types.SignatureV1{*NewNamespace(t, vk), Signature}) + } + return types.NewCosignedTreeHeadV1(DefaultSth(t, logVk).SignedTreeHeadV1, cosigs) +} + +func DefaultSth(t *testing.T, vk [32]byte) *types.StItem { + t.Helper() + return types.NewSignedTreeHeadV1(DefaultTh(t), DefaultSig(t, vk)) +} + +func DefaultSignedChecksum(t *testing.T, vk [32]byte) *types.StItem { + t.Helper() + return types.NewSignedChecksumV1(DefaultChecksum(t), DefaultSig(t, vk)) +} + +func DefaultTh(t *testing.T) *types.TreeHeadV1 { + t.Helper() + return types.NewTreeHeadV1(Timestamp, TreeSize, NodeHash, Extension) +} + +func DefaultSig(t *testing.T, vk [32]byte) *types.SignatureV1 { + t.Helper() + return &types.SignatureV1{*NewNamespace(t, vk), Signature} +} + +func DefaultChecksum(t *testing.T) *types.ChecksumV1 { + t.Helper() + return &types.ChecksumV1{Identifier, Checksum} +} + +func AddCosignatureBuffer(t *testing.T, sth *types.StItem, sk *[64]byte, vk *[32]byte) *bytes.Buffer { + t.Helper() + var cosigs []types.SignatureV1 + if vk != nil { + cosigs = []types.SignatureV1{ + types.SignatureV1{ + Namespace: *NewNamespace(t, *vk), + Signature: ed25519.Sign(ed25519.PrivateKey((*sk)[:]), marshal(t, *sth.SignedTreeHeadV1)), + }, + } + } + return bytes.NewBuffer(marshal(t, *types.NewCosignedTreeHeadV1(sth.SignedTreeHeadV1, cosigs))) +} + +func AddSignedChecksumBuffer(t *testing.T, sk [64]byte, vk [32]byte) *bytes.Buffer { + t.Helper() + data := DefaultChecksum(t) + return bytes.NewBuffer(marshal(t, *types.NewSignedChecksumV1( + data, + &types.SignatureV1{ + Namespace: *NewNamespace(t, vk), + Signature: ed25519.Sign(ed25519.PrivateKey(sk[:]), marshal(t, *data)), + }, + ))) +} + +func NewNamespacePool(t *testing.T, namespaces []*types.Namespace) *types.NamespacePool { + pool, err := types.NewNamespacePool(namespaces) + if err != nil { + t.Fatalf("must make namespace pool: %v", err) + } + return pool +} + +func NewNamespace(t *testing.T, vk [32]byte) *types.Namespace { + namespace, err := types.NewNamespaceEd25519V1(vk[:]) + if err != nil { + t.Fatalf("must make Ed25519V1 namespace: %v", err) + } + return namespace +} + +// +// Helpers that must create default values for different Trillian types +// + +// DefaultTLr creates a default Trillian log root +func DefaultTLr(t *testing.T) *ttypes.LogRootV1 { + t.Helper() + return Tlr(t, TreeSize, Timestamp, NodeHash) +} + +// Tlr creates a Trillian log root +func Tlr(t *testing.T, size, timestamp uint64, hash []byte) *ttypes.LogRootV1 { + t.Helper() + return &ttypes.LogRootV1{ + TreeSize: size, + RootHash: hash, + TimestampNanos: timestamp, + Revision: 0, // not used by stfe + Metadata: nil, // not used by stfe + } +} + +// DefaultTSlr creates a default Trillian signed log root +func DefaultTSlr(t *testing.T) *trillian.GetLatestSignedLogRootResponse { + t.Helper() + return Tslr(t, DefaultTLr(t)) +} + +// Tslr creates a Trillian signed log root +func Tslr(t *testing.T, lr *ttypes.LogRootV1) *trillian.GetLatestSignedLogRootResponse { + t.Helper() + b, err := lr.MarshalBinary() + if err != nil { + t.Fatalf("must marshal Trillian log root: %v", err) + } + return &trillian.GetLatestSignedLogRootResponse{ + SignedLogRoot: &trillian.SignedLogRoot{ + KeyHint: nil, // not used by stfe + LogRoot: b, + LogRootSignature: nil, // not used by stfe + }, + Proof: nil, // not used by stfe + } +} + +// DefaultTQlr creates a default Trillian queue leaf response +func DefaultTQlr(t *testing.T, withDupCode bool) *trillian.QueueLeafResponse { + t.Helper() + s := status.New(codes.OK, "ok").Proto() + if withDupCode { + s = status.New(codes.AlreadyExists, "duplicate").Proto() + } + return &trillian.QueueLeafResponse{ + QueuedLeaf: &trillian.QueuedLogLeaf{ + Leaf: &trillian.LogLeaf{ + MerkleLeafHash: nil, // not used by stfe + LeafValue: marshal(t, *DefaultSignedChecksum(t, Ed25519VkSubmitter)), + ExtraData: nil, // not used by stfe + LeafIndex: 0, // not applicable (log is not pre-ordered) + LeafIdentityHash: nil, // not used by stfe + }, + Status: s, + }, + } +} + +// DefaultTglbrr creates a default Trillian get leaves by range response +func DefaultTGlbrr(t *testing.T, start, end int64) *trillian.GetLeavesByRangeResponse { + t.Helper() + leaves := make([]*trillian.LogLeaf, 0, end-start+1) + for i, n := start, end+1; i < n; i++ { + leaves = append(leaves, &trillian.LogLeaf{ + MerkleLeafHash: nil, // not usedb y stfe + LeafValue: marshal(t, *DefaultSignedChecksum(t, Ed25519VkSubmitter)), + ExtraData: nil, // not used by stfe + LeafIndex: i, + LeafIdentityHash: nil, // not used by stfe + }) + } + return &trillian.GetLeavesByRangeResponse{ + Leaves: leaves, + SignedLogRoot: Tslr(t, Tlr(t, uint64(end)+1, Timestamp, NodeHash)).SignedLogRoot, + } +} + +func DefaultStItemList(t *testing.T, start, end uint64) *types.StItemList { + items := make([]types.StItem, 0, end-start+1) + for i, n := start, end+1; i < n; i++ { + items = append(items, *DefaultSignedChecksum(t, Ed25519VkSubmitter)) + } + return &types.StItemList{items} +} + +// DefaultTGipbhr creates a default Trillian get inclusion proof by hash response +func DefaultTGipbhr(t *testing.T) *trillian.GetInclusionProofByHashResponse { + t.Helper() + return &trillian.GetInclusionProofByHashResponse{ + Proof: []*trillian.Proof{ + &trillian.Proof{ + LeafIndex: Index, + Hashes: HashPath, + }, + }, + SignedLogRoot: nil, // not used by stfe + } +} + +func DefaultInclusionProof(t *testing.T, size uint64) *types.StItem { + return types.NewInclusionProofV1(NewNamespace(t, Ed25519VkLog), size, uint64(Index), NodePath) +} + +// DefaultTGcpr creates a default Trillian get consistency proof response +func DefaultTGcpr(t *testing.T) *trillian.GetConsistencyProofResponse { + t.Helper() + return &trillian.GetConsistencyProofResponse{ + Proof: &trillian.Proof{ + LeafIndex: 0, // not applicable for consistency proofs + Hashes: HashPath, + }, + SignedLogRoot: nil, // not used by stfe + } +} + +func DefaultConsistencyProof(t *testing.T, first, second uint64) *types.StItem { + return types.NewConsistencyProofV1(NewNamespace(t, Ed25519VkLog), first, second, NodePath) +} + +// +// Other helpers +// + +func Fingerprint(t *testing.T, namespace *types.Namespace) [types.NamespaceFingerprintSize]byte { + fpr, err := namespace.Fingerprint() + if err != nil { + t.Fatalf("must have namespace fingerprint: %v", err) + } + return *fpr +} + +func marshal(t *testing.T, i interface{}) []byte { + b, err := types.Marshal(i) + if err != nil { + t.Fatalf("must marshal interface: %v", err) + } + return b +} diff --git a/trillian.go b/trillian.go index 162ec77..2adf567 100644 --- a/trillian.go +++ b/trillian.go @@ -3,11 +3,10 @@ package stfe import ( "fmt" - "net/http" - "github.com/golang/glog" "github.com/google/trillian" "github.com/google/trillian/types" + stfetypes "github.com/system-transparency/stfe/types" "google.golang.org/grpc/codes" ) @@ -27,42 +26,42 @@ func checkQueueLeaf(rsp *trillian.QueueLeafResponse, err error) error { return nil } -func checkGetLeavesByRange(req *GetEntriesRequest, rsp *trillian.GetLeavesByRangeResponse, err error) (int, error) { +func checkGetLeavesByRange(req *stfetypes.GetEntriesV1, rsp *trillian.GetLeavesByRangeResponse, err error) error { if err != nil { - return http.StatusInternalServerError, fmt.Errorf("Trillian Error: %v", err) + return fmt.Errorf("Trillian Error: %v", err) } if rsp == nil { - return http.StatusInternalServerError, fmt.Errorf("Trillian error: empty response") + return fmt.Errorf("Trillian error: empty response") } if rsp.SignedLogRoot == nil { - return http.StatusInternalServerError, fmt.Errorf("Trillian error: no signed log root") + return fmt.Errorf("Trillian error: no signed log root") } if rsp.SignedLogRoot.LogRoot == nil { - return http.StatusInternalServerError, fmt.Errorf("Trillian error: no log root") + return fmt.Errorf("Trillian error: no log root") } if len(rsp.Leaves) == 0 { - return http.StatusInternalServerError, fmt.Errorf("Trillian error: no leaves") + return fmt.Errorf("Trillian error: no leaves") } if len(rsp.Leaves) > int(req.End-req.Start+1) { - return http.StatusInternalServerError, fmt.Errorf("too many leaves: %d for [%d,%d]", len(rsp.Leaves), req.Start, req.End) + return fmt.Errorf("too many leaves: %d for [%d,%d]", len(rsp.Leaves), req.Start, req.End) } // Ensure that a bad start parameter results in an error var lr types.LogRootV1 if err := lr.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { - return http.StatusInternalServerError, fmt.Errorf("cannot unmarshal log root: %v", err) + return fmt.Errorf("cannot unmarshal log root: %v", err) } if uint64(req.Start) >= lr.TreeSize { - return http.StatusNotFound, fmt.Errorf("invalid start(%d): tree size is %d", req.Start, lr.TreeSize) + return fmt.Errorf("invalid start(%d): tree size is %d", req.Start, lr.TreeSize) } // Ensure that we got and return expected leaf indices for i, leaf := range rsp.Leaves { - if leaf.LeafIndex != req.Start+int64(i) { - return http.StatusInternalServerError, fmt.Errorf("invalid leaf index: wanted %d, got %d", req.Start+int64(i), leaf.LeafIndex) + if got, want := leaf.LeafIndex, int64(req.Start+uint64(i)); got != want { + return fmt.Errorf("invalid leaf index(%d): wanted %d", got, want) } } - return http.StatusOK, nil + return nil } func checkGetInclusionProofByHash(lp *LogParameters, rsp *trillian.GetInclusionProofByHashResponse, err error) error { diff --git a/trillian_test.go b/trillian_test.go index b3c1653..1b0c923 100644 --- a/trillian_test.go +++ b/trillian_test.go @@ -5,11 +5,9 @@ import ( "testing" "github.com/google/trillian" - "github.com/google/trillian/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/system-transparency/stfe/namespace/testdata" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/testdata" + "github.com/system-transparency/stfe/types" ) func TestCheckQueueLeaf(t *testing.T) { @@ -20,30 +18,31 @@ func TestCheckQueueLeaf(t *testing.T) { wantErr bool }{ { - description: "bad response: trillian error", + description: "invalid: no Trillian response: error", err: fmt.Errorf("backend error"), wantErr: true, }, { - description: "bad response: empty", + description: "invalid: no Trillian response: nil", wantErr: true, }, { - description: "bad response: no queued leaf", + description: "invalid: no Trillian response: empty", rsp: &trillian.QueueLeafResponse{}, wantErr: true, }, { - description: "ok response: duplicate leaf", - rsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk, true), + description: "valid: gRPC status: duplicate", + rsp: testdata.DefaultTQlr(t, true), }, { - description: "ok response: new leaf", - rsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk, false), + description: "valid: gRPC status: ok", + rsp: testdata.DefaultTQlr(t, false), }, } { - if err := checkQueueLeaf(table.rsp, table.err); (err != nil) != table.wantErr { - t.Errorf("got error %v, but wanted error %v in test %q", err, table.wantErr, table.description) + err := checkQueueLeaf(table.rsp, table.err) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) } } } @@ -51,101 +50,90 @@ func TestCheckQueueLeaf(t *testing.T) { func TestCheckGetLeavesByRange(t *testing.T) { for _, table := range []struct { description string - req *GetEntriesRequest + req *types.GetEntriesV1 rsp *trillian.GetLeavesByRangeResponse err error wantErr bool }{ { - description: "bad response: trillian error", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: no Trillian response: error", + req: &types.GetEntriesV1{Start: 0, End: 1}, err: fmt.Errorf("backend error"), wantErr: true, }, { - description: "bad response: empty", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: no Trillian response: nil", + req: &types.GetEntriesV1{Start: 0, End: 1}, wantErr: true, }, { - description: "bad response: no leaves", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: bad Trillian response: no leaves", + req: &types.GetEntriesV1{Start: 0, End: 1}, rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { rsp.Leaves = nil return rsp - }(makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), + }(testdata.DefaultTGlbrr(t, 0, 1)), wantErr: true, }, { - description: "bad response: no signed log root", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: bad Trillian response: no signed log root", + req: &types.GetEntriesV1{Start: 0, End: 1}, rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { rsp.SignedLogRoot = nil return rsp - }(makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), + }(testdata.DefaultTGlbrr(t, 0, 1)), wantErr: true, }, { - description: "bad response: no log root", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: bad Trillian response: no log root", + req: &types.GetEntriesV1{Start: 0, End: 1}, rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { rsp.SignedLogRoot.LogRoot = nil return rsp - }(makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), + }(testdata.DefaultTGlbrr(t, 0, 1)), wantErr: true, }, { - description: "bad response: truncated root", - req: &GetEntriesRequest{Start: 0, End: 1}, + description: "invalid: bad Trillian response: truncated log root", + req: &types.GetEntriesV1{Start: 0, End: 1}, rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { rsp.SignedLogRoot.LogRoot = rsp.SignedLogRoot.LogRoot[1:] return rsp - }(makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), + }(testdata.DefaultTGlbrr(t, 0, 1)), wantErr: true, }, { - description: "bad response: too many leaves", - req: &GetEntriesRequest{Start: 0, End: 1}, - rsp: makeTrillianGetLeavesByRangeResponse(t, 0, 2, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk), + description: "invalid: bad Trillian response: too many leaves", + req: &types.GetEntriesV1{Start: 0, End: 1}, + rsp: testdata.DefaultTGlbrr(t, 0, 2), wantErr: true, }, { - description: "bad response: start is not a valid index", - req: &GetEntriesRequest{Start: int64(testTreeSize), End: int64(testTreeSize)}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.SignedLogRoot = makeLatestSignedLogRootResponse(t, 0, testTreeSize, testNodeHash).SignedLogRoot - return rsp - }(makeTrillianGetLeavesByRangeResponse(t, int64(testTreeSize)-1, int64(testTreeSize)-1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), - wantErr: true, - }, - { - description: "bad response: invalid leaf indices", - req: &GetEntriesRequest{Start: 10, End: 11}, - rsp: makeTrillianGetLeavesByRangeResponse(t, 11, 12, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk), + description: "invalid: bad Trillian response: start is not a valid index", + req: &types.GetEntriesV1{Start: 10, End: 10}, + rsp: testdata.DefaultTGlbrr(t, 9, 9), wantErr: true, }, { - description: "ok response: interval refers to the latest leaf", - req: &GetEntriesRequest{Start: int64(testTreeSize) - 1, End: int64(testTreeSize) - 1}, - rsp: func(rsp *trillian.GetLeavesByRangeResponse) *trillian.GetLeavesByRangeResponse { - rsp.SignedLogRoot = makeLatestSignedLogRootResponse(t, 0, testTreeSize, testNodeHash).SignedLogRoot - return rsp - }(makeTrillianGetLeavesByRangeResponse(t, int64(testTreeSize)-1, int64(testTreeSize)-1, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk)), + description: "invalid: bad Trillian response: invalid leaf indices", + req: &types.GetEntriesV1{Start: 10, End: 11}, + rsp: testdata.DefaultTGlbrr(t, 11, 12), + wantErr: true, }, { - description: "ok response: a bunch of leaves", - req: &GetEntriesRequest{Start: 10, End: 20}, - rsp: makeTrillianGetLeavesByRangeResponse(t, 10, 20, testPackage, testdata.Ed25519Vk, testdata.Ed25519Sk), + description: "valid", + req: &types.GetEntriesV1{Start: 10, End: 20}, + rsp: testdata.DefaultTGlbrr(t, 10, 20), }, } { - if _, err := checkGetLeavesByRange(table.req, table.rsp, table.err); (err != nil) != table.wantErr { - t.Errorf("got error %v, but wanted error %v in test %q", err, table.wantErr, table.description) + err := checkGetLeavesByRange(table.req, table.rsp, table.err) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) } } } func TestCheckGetInclusionProofByHash(t *testing.T) { - lp := makeTestLogParameters(t, nil) for _, table := range []struct { description string rsp *trillian.GetInclusionProofByHashResponse @@ -153,16 +141,16 @@ func TestCheckGetInclusionProofByHash(t *testing.T) { wantErr bool }{ { - description: "bad response: trillian error", + description: "invalid: no Trillian response: error", err: fmt.Errorf("backend failure"), wantErr: true, }, { - description: "bad response: empty", + description: "invalid: no Trillian response: nil", wantErr: true, }, { - description: "bad response: no proofs", + description: "invalid: bad Trillian response: no proofs", rsp: &trillian.GetInclusionProofByHashResponse{}, wantErr: true, }, @@ -171,27 +159,30 @@ func TestCheckGetInclusionProofByHash(t *testing.T) { rsp: func(rsp *trillian.GetInclusionProofByHashResponse) *trillian.GetInclusionProofByHashResponse { rsp.Proof[0] = nil return rsp - }(makeTrillianGetInclusionProofByHashResponse(t, int64(testIndex), testProof)), + }(testdata.DefaultTGipbhr(t)), wantErr: true, }, { description: "bad response: proof with invalid node hash", - rsp: makeTrillianGetInclusionProofByHashResponse(t, int64(testIndex), [][]byte{make([]byte, testHashLen-1)}), - wantErr: true, + rsp: func(rsp *trillian.GetInclusionProofByHashResponse) *trillian.GetInclusionProofByHashResponse { + rsp.Proof[0].Hashes = append(rsp.Proof[0].Hashes, make([]byte, 0)) + return rsp + }(testdata.DefaultTGipbhr(t)), + wantErr: true, }, { - description: "ok response", - rsp: makeTrillianGetInclusionProofByHashResponse(t, int64(testIndex), testProof), + description: "valid", + rsp: testdata.DefaultTGipbhr(t), }, } { - if err := checkGetInclusionProofByHash(lp, table.rsp, table.err); (err != nil) != table.wantErr { - t.Errorf("got error %v, but wanted error %v in test %q", err, table.wantErr, table.description) + err := checkGetInclusionProofByHash(newLogParameters(t, nil), table.rsp, table.err) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) } } } func TestCheckGetConsistencyProof(t *testing.T) { - lp := makeTestLogParameters(t, nil) for _, table := range []struct { description string rsp *trillian.GetConsistencyProofResponse @@ -199,37 +190,40 @@ func TestCheckGetConsistencyProof(t *testing.T) { wantErr bool }{ { - description: "bad response: trillian error", + description: "invalid: no Trillian response: error", err: fmt.Errorf("backend failure"), wantErr: true, }, { - description: "bad response: empty", + description: "invalid: no Trillian response: nil", wantErr: true, }, { - description: "bad response: no proof", + description: "invalid: bad Trillian response: no proof", rsp: &trillian.GetConsistencyProofResponse{}, wantErr: true, }, { - description: "bad response: proof with invalid node hash", - rsp: makeTrillianGetConsistencyProofResponse(t, [][]byte{make([]byte, testHashLen-1)}), - wantErr: true, + description: "invalid: bad Trillian response: proof with invalid node hash", + rsp: func(rsp *trillian.GetConsistencyProofResponse) *trillian.GetConsistencyProofResponse { + rsp.Proof.Hashes = append(rsp.Proof.Hashes, make([]byte, 0)) + return rsp + }(testdata.DefaultTGcpr(t)), + wantErr: true, }, { - description: "ok response", - rsp: makeTrillianGetConsistencyProofResponse(t, testProof), + description: "valid", + rsp: testdata.DefaultTGcpr(t), }, } { - if err := checkGetConsistencyProof(lp, table.rsp, table.err); (err != nil) != table.wantErr { - t.Errorf("got error %v, but wanted error %v in test %q", err, table.wantErr, table.description) + err := checkGetConsistencyProof(newLogParameters(t, nil), table.rsp, table.err) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) } } } func TestCheckGetLatestSignedLogRoot(t *testing.T) { - lp := makeTestLogParameters(t, nil) for _, table := range []struct { description string rsp *trillian.GetLatestSignedLogRootResponse @@ -237,160 +231,52 @@ func TestCheckGetLatestSignedLogRoot(t *testing.T) { wantErr bool }{ { - description: "bad trillian response: error", + description: "invalid: no Trillian response: error", err: fmt.Errorf("backend failure"), wantErr: true, }, { - description: "bad trillian response: empty", + description: "invalid: no Trillian response: nil", wantErr: true, }, { - description: "bad trillian response: no signed log root", - rsp: &trillian.GetLatestSignedLogRootResponse{SignedLogRoot: nil}, - wantErr: true, + description: "invalid: bad Trillian response: no signed log root", + rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { + rsp.SignedLogRoot = nil + return rsp + }(testdata.DefaultTSlr(t)), + wantErr: true, }, { - description: "bad trillian response: no log root", + description: "invalid: bad Trillian response: no log root", rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { rsp.SignedLogRoot.LogRoot = nil return rsp - }(makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash)), + }(testdata.DefaultTSlr(t)), wantErr: true, }, { - description: "bad trillian response: truncated log root", + description: "invalid: bad Trillian response: truncated log root", rsp: func(rsp *trillian.GetLatestSignedLogRootResponse) *trillian.GetLatestSignedLogRootResponse { rsp.SignedLogRoot.LogRoot = rsp.SignedLogRoot.LogRoot[1:] return rsp - }(makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash)), + }(testdata.DefaultTSlr(t)), wantErr: true, }, { - description: "bad trillian response: invalid root hash size", - rsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, testHashLen-1)), + description: "invalid: bad Trillian response: truncated root hash", + rsp: testdata.Tslr(t, testdata.Tlr(t, testdata.TreeSize, testdata.Timestamp, make([]byte, 31))), wantErr: true, }, { - description: "ok response", - rsp: makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash), + description: "valid", + rsp: testdata.DefaultTSlr(t), }, } { - var lr types.LogRootV1 - if err := checkGetLatestSignedLogRoot(lp, table.rsp, table.err, &lr); (err != nil) != table.wantErr { - t.Errorf("got error %v, but wanted error %v in test %q", err, table.wantErr, table.description) + var lr ttypes.LogRootV1 + err := checkGetLatestSignedLogRoot(newLogParameters(t, nil), table.rsp, table.err, &lr) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q", got, want, table.description) } } } - -// makeTrillianQueueLeafResponse creates a valid trillian QueueLeafResponse -// for a package `name` where the checksum is all zeros (32 bytes). Namespace -// is based on vk and sk (ed25519). -// -// Note: MerkleLeafHash and LeafIdentityHash are unset (not used by stfe). -func makeTrillianQueueLeafResponse(t *testing.T, name, vk, sk []byte, dupCode bool) *trillian.QueueLeafResponse { - t.Helper() - leaf, signature := mustMakeEd25519ChecksumV1(t, name, make([]byte, 32), vk, sk) - s := status.New(codes.OK, "ok").Proto() - if dupCode { - s = status.New(codes.AlreadyExists, "duplicate").Proto() - } - return &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - MerkleLeafHash: nil, // not used by stfe - LeafValue: leaf, - ExtraData: signature, - LeafIndex: 0, // not applicable (log is not pre-ordered) - LeafIdentityHash: nil, // not used by stfe - }, - Status: s, - }, - } -} - -// makeTrillianGetInclusionProofByHashResponse populates a get-proof-by-hash -// response. -// -// Note: SignedLogRoot is unset (not used by stfe). -func makeTrillianGetInclusionProofByHashResponse(t *testing.T, index int64, path [][]byte) *trillian.GetInclusionProofByHashResponse { - t.Helper() - return &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: index, - Hashes: path, - }, - }, - SignedLogRoot: nil, - } -} - -// makeTrillianGetConsistencyProofResponse populates a get-consistency response. -// -// Note: LeafIndex is not applicable for a consistency proof (0), and -// SignedLogRoot is unset (not used by stfe). -func makeTrillianGetConsistencyProofResponse(t *testing.T, path [][]byte) *trillian.GetConsistencyProofResponse { - t.Helper() - return &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - LeafIndex: 0, - Hashes: path, - }, - SignedLogRoot: nil, - } -} - -// makeTrillianGetLeavesByRangeResponse creates a range of leaves [start,end] -// such that the package is `name`_ and the checksum is all zeros (32 -// bytes). An Ed25519 namespace is used based on vk and sk. -// -// Note: MerkleLeafHash and LeafIdentityHash are unset (not used by stfe). -func makeTrillianGetLeavesByRangeResponse(t *testing.T, start, end int64, name, vk, sk []byte) *trillian.GetLeavesByRangeResponse { - t.Helper() - leaves := make([]*trillian.LogLeaf, 0, end-start+1) - for i, n := start, end+1; i < n; i++ { - leaf, signature := mustMakeEd25519ChecksumV1(t, append(name, []byte(fmt.Sprintf("_%d", i))...), make([]byte, 32), vk, sk) - leaves = append(leaves, &trillian.LogLeaf{ - MerkleLeafHash: nil, - LeafValue: leaf, - ExtraData: signature, - LeafIndex: i, - LeafIdentityHash: nil, - }) - } - return &trillian.GetLeavesByRangeResponse{ - Leaves: leaves, - SignedLogRoot: makeLatestSignedLogRootResponse(t, 0, uint64(end)+1, make([]byte, 32)).SignedLogRoot, - } -} - -// makeTrillianLogRoot: docdoc -func makeTrillianLogRoot(t *testing.T, timestamp, size uint64, hash []byte) *types.LogRootV1 { - t.Helper() - return &types.LogRootV1{ - TreeSize: size, - RootHash: hash, - TimestampNanos: timestamp, - Revision: 0, // not used by stfe - Metadata: nil, // not used by stfe - } -} - -// makeLatestSignedLogRootResponse creates a new trillian STH. Revision, -// Metadata, Proof, KeyHint, and LogRootSignature are unsset. -func makeLatestSignedLogRootResponse(t *testing.T, timestamp, size uint64, hash []byte) *trillian.GetLatestSignedLogRootResponse { - t.Helper() - rootBytes, err := makeTrillianLogRoot(t, timestamp, size, hash).MarshalBinary() - if err != nil { - t.Fatalf("failed to marshal root in test: %v", err) - } - return &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - KeyHint: nil, // not used by stfe - LogRoot: rootBytes, - LogRootSignature: nil, // not used by stfe - }, - Proof: nil, // not used by stfe - } -} diff --git a/type.go b/type.go deleted file mode 100644 index db9ddc6..0000000 --- a/type.go +++ /dev/null @@ -1,306 +0,0 @@ -package stfe - -import ( - "fmt" - "time" - - "encoding/base64" - - "github.com/google/certificate-transparency-go/tls" - "github.com/google/trillian/types" - "github.com/system-transparency/stfe/namespace" -) - -// StFormat defines a particular StItem type that is versioned -type StFormat tls.Enum - -const ( - StFormatReserved StFormat = 0 - StFormatSignedTreeHeadV1 StFormat = 1 - StFormatSignedDebugInfoV1 StFormat = 2 - StFormatConsistencyProofV1 StFormat = 3 - StFormatInclusionProofV1 StFormat = 4 - StFormatChecksumV1 = 5 - StFormatCosignedTreeHeadV1 = 6 -) - -// StItem references a versioned item based on a given format specifier -type StItem struct { - Format StFormat `tls:"maxval:65535"` - SignedTreeHeadV1 *SignedTreeHeadV1 `tls:"selector:Format,val:1"` - SignedDebugInfoV1 *SignedDebugInfoV1 `tls:"selector:Format,val:2"` - ConsistencyProofV1 *ConsistencyProofV1 `tls:"selector:Format,val:3"` - InclusionProofV1 *InclusionProofV1 `tls:"selector:Format,val:4"` - ChecksumV1 *ChecksumV1 `tls:"selector:Format,val:5"` - CosignedTreeHeadV1 *CosignedTreeHeadV1 `tls:"selector:Format,val:6"` -} - -// SignedTreeHeadV1 is a signed tree head as defined by RFC 6962/bis, §4.10 -type SignedTreeHeadV1 struct { - LogId []byte `tls:"minlen:35,maxlen:35"` - TreeHead TreeHeadV1 - Signature []byte `tls:"minlen:1,maxlen:65535"` -} - -// SignedDebugInfoV1 is a signed statement that we intend (but do not promise) -// to insert an entry into the log as defined by markdown/api.md -type SignedDebugInfoV1 struct { - LogId []byte `tls:"minlen:35,maxlen:35"` - Message []byte `tls:"minlen:0,maxlen:65535"` - Signature []byte `tls:"minlen:1,maxlen:65535"` -} - -// ConsistencyProofV1 is a consistency proof as defined by RFC 6962/bis, §4.11 -type ConsistencyProofV1 struct { - LogId []byte `tls:"minlen:35,maxlen:35"` - TreeSize1 uint64 - TreeSize2 uint64 - ConsistencyPath []NodeHash `tls:"minlen:0,maxlen:65535"` -} - -// InclusionProofV1 is an inclusion proof as defined by RFC 6962/bis, §4.12 -type InclusionProofV1 struct { - LogId []byte `tls:"minlen:35,maxlen:35"` - TreeSize uint64 - LeafIndex uint64 - InclusionPath []NodeHash `tls:"minlen:0,maxlen:65535"` -} - -// ChecksumV1 associates a leaf type as defined by markdown/api.md -type ChecksumV1 struct { - Package []byte `tls:"minlen:1,maxlen:255"` - Checksum []byte `tls:"minlen:1,maxlen:64"` - Namespace namespace.Namespace -} - -// TreeHeadV1 is a tree head as defined by RFC 6962/bis, §4.10 -type TreeHeadV1 struct { - Timestamp uint64 - TreeSize uint64 - RootHash NodeHash - Extension []byte `tls:"minlen:0,maxlen:65535"` -} - -// CosignedTreeheadV1 is a cosigned STH -type CosignedTreeHeadV1 struct { - SignedTreeHeadV1 SignedTreeHeadV1 - SignatureV1 []SignatureV1 `tls:"minlen:0,maxlen:4294967295"` -} - -// SignatureV1 is a detached signature that was produced by a namespace -type SignatureV1 struct { - Namespace namespace.Namespace - Signature []byte `tls:"minlen:1,maxlen:65535"` -} - -// NodeHash is a Merkle tree hash as defined by RFC 6962/bis, §4.9 -type NodeHash struct { - Data []byte `tls:"minlen:32,maxlen:255"` -} - -// RawCertificate is a serialized X.509 certificate -type RawCertificate struct { - Data []byte `tls:"minlen:0,maxlen:65535"` -} - -func (f StFormat) String() string { - switch f { - case StFormatReserved: - return "reserved" - case StFormatSignedTreeHeadV1: - return "signed_tree_head_v1" - case StFormatSignedDebugInfoV1: - return "signed_debug_info_v1" - case StFormatConsistencyProofV1: - return "consistency_proof_v1" - case StFormatInclusionProofV1: - return "inclusion_proof_v1" - case StFormatChecksumV1: - return "checksum_v1" - case StFormatCosignedTreeHeadV1: - return "cosigned_tree_head_v1" - default: - return fmt.Sprintf("Unknown StFormat: %d", f) - } -} - -func (i StItem) String() string { - switch i.Format { - case StFormatChecksumV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.ChecksumV1) - case StFormatConsistencyProofV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.ConsistencyProofV1) - case StFormatInclusionProofV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.InclusionProofV1) - case StFormatSignedDebugInfoV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.SignedDebugInfoV1) - case StFormatSignedTreeHeadV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.SignedTreeHeadV1) - case StFormatCosignedTreeHeadV1: - return fmt.Sprintf("Format(%s): %s", i.Format, i.CosignedTreeHeadV1) - default: - return fmt.Sprintf("unknown StItem: %s", i.Format) - } -} - -func (i SignedTreeHeadV1) String() string { - return fmt.Sprintf("LogId(%s) TreeHead(%s) Signature(%s)", b64(i.LogId), i.TreeHead, b64(i.Signature)) -} - -func (i SignedDebugInfoV1) String() string { - return fmt.Sprintf("LogId(%s) Message(%s) Signature(%s)", b64(i.LogId), string(i.Message), b64(i.Signature)) -} - -func (i ConsistencyProofV1) String() string { - return fmt.Sprintf("LogID(%s) TreeSize1(%d) TreeSize2(%d) ConsistencyPath(%v)", b64(i.LogId), i.TreeSize1, i.TreeSize2, B64EncodePath(i.ConsistencyPath)) -} - -func (i InclusionProofV1) String() string { - return fmt.Sprintf("LogID(%s) TreeSize(%d) LeafIndex(%d) AuditPath(%v)", b64(i.LogId), i.TreeSize, i.LeafIndex, B64EncodePath(i.InclusionPath)) -} - -func (i ChecksumV1) String() string { - return fmt.Sprintf("Package(%s) Checksum(%s) Namespace(%s)", string(i.Package), string(i.Checksum), i.Namespace.String()) -} - -func (th TreeHeadV1) String() string { - return fmt.Sprintf("Timestamp(%s) TreeSize(%d) RootHash(%s)", time.Unix(int64(th.Timestamp/1000), 0), th.TreeSize, b64(th.RootHash.Data)) -} - -func (i CosignedTreeHeadV1) String() string { - return fmt.Sprintf("SignedTreeHead(%s) #Cosignatures(%d)", i.SignedTreeHeadV1.String(), len(i.SignatureV1)) -} - -// Marshal serializes an Stitem as defined by RFC 5246 -func (i *StItem) Marshal() ([]byte, error) { - serialized, err := tls.Marshal(*i) - if err != nil { - return nil, fmt.Errorf("marshal failed for StItem(%s): %v", i.Format, err) - } - return serialized, nil -} - -// MarshalB64 base64-encodes a serialized StItem -func (i *StItem) MarshalB64() (string, error) { - serialized, err := i.Marshal() - if err != nil { - return "", err - } - return b64(serialized), nil -} - -// Unmarshal unpacks a serialized StItem -func (i *StItem) Unmarshal(serialized []byte) error { - extra, err := tls.Unmarshal(serialized, i) - if err != nil { - return fmt.Errorf("unmarshal failed for StItem(%s): %v", i.Format, err) - } else if len(extra) > 0 { - return fmt.Errorf("unmarshal found extra data for StItem(%s): %v", i.Format, extra) - } - return nil -} - -// UnmarshalB64 unpacks a base64-encoded serialized StItem -func (i *StItem) UnmarshalB64(s string) error { - serialized, err := deb64(s) - if err != nil { - return fmt.Errorf("base64 decoding failed for StItem(%s): %v", i.Format, err) - } - return i.Unmarshal(serialized) -} - -// Marshal serializes a TreeHeadV1 as defined by RFC 5246 -func (th *TreeHeadV1) Marshal() ([]byte, error) { - serialized, err := tls.Marshal(*th) - if err != nil { - return nil, fmt.Errorf("marshal failed for TreeHeadV1: %v", err) - } - return serialized, nil -} - -// B64EncodePath encodes a path of node hashes as a list of base64 strings -func B64EncodePath(path []NodeHash) []string { - p := make([]string, 0, len(path)) - for _, hash := range path { - p = append(p, b64(hash.Data)) - } - return p -} - -// NewSignedTreeHead creates a new StItem of type signed_tree_head_v1 -func NewSignedTreeHeadV1(th *TreeHeadV1, logId, signature []byte) *StItem { - return &StItem{ - Format: StFormatSignedTreeHeadV1, - SignedTreeHeadV1: &SignedTreeHeadV1{logId, *th, signature}, - } -} - -// NewSignedDebugInfoV1 creates a new StItem of type inclusion_proof_v1 -func NewSignedDebugInfoV1(logId, message, signature []byte) *StItem { - return &StItem{ - Format: StFormatSignedDebugInfoV1, - SignedDebugInfoV1: &SignedDebugInfoV1{logId, message, signature}, - } -} - -// NewInclusionProofV1 creates a new StItem of type inclusion_proof_v1 -func NewInclusionProofV1(logID []byte, treeSize, index uint64, proof [][]byte) *StItem { - path := make([]NodeHash, 0, len(proof)) - for _, hash := range proof { - path = append(path, NodeHash{Data: hash}) - } - return &StItem{ - Format: StFormatInclusionProofV1, - InclusionProofV1: &InclusionProofV1{logID, treeSize, index, path}, - } -} - -// NewConsistencyProofV1 creates a new StItem of type consistency_proof_v1 -func NewConsistencyProofV1(logId []byte, first, second uint64, proof [][]byte) *StItem { - path := make([]NodeHash, 0, len(proof)) - for _, hash := range proof { - path = append(path, NodeHash{Data: hash}) - } - return &StItem{ - Format: StFormatConsistencyProofV1, - ConsistencyProofV1: &ConsistencyProofV1{logId, uint64(first), uint64(second), path}, - } -} - -// NewChecksumV1 creates a new StItem of type checksum_v1 -func NewChecksumV1(identifier, checksum []byte, namespace *namespace.Namespace) *StItem { - return &StItem{ - Format: StFormatChecksumV1, - ChecksumV1: &ChecksumV1{identifier, checksum, *namespace}, - } -} - -// NewTreeHead creates a new TreeHeadV1 from a Trillian-signed log root without -// verifying any signature. In other words, Trillian <-> STFE must be trusted. -func NewTreeHeadV1(lr *types.LogRootV1) *TreeHeadV1 { - return &TreeHeadV1{ - uint64(lr.TimestampNanos / 1000 / 1000), - uint64(lr.TreeSize), - NodeHash{lr.RootHash}, - make([]byte, 0), - } -} - -// NewCosignedTreeHeadV1 creates a new StItem of type cosigned_tree_head_v1 -func NewCosignedTreeHeadV1(sth *SignedTreeHeadV1, sigs []SignatureV1) *StItem { - return &StItem{ - Format: StFormatCosignedTreeHeadV1, - CosignedTreeHeadV1: &CosignedTreeHeadV1{ - SignedTreeHeadV1: *sth, - SignatureV1: sigs, - }, - } -} - -func b64(b []byte) string { - return base64.StdEncoding.EncodeToString(b) -} - -func deb64(str string) ([]byte, error) { - return base64.StdEncoding.DecodeString(str) -} diff --git a/type_test.go b/type_test.go deleted file mode 100644 index 6ac3b29..0000000 --- a/type_test.go +++ /dev/null @@ -1,330 +0,0 @@ -package stfe - -import ( - "testing" - - "github.com/system-transparency/stfe/namespace/testdata" -) - -// TestEncDecStItem tests that valid StItems can be (un)marshaled, and that -// invalid ones in fact fail. -// -// Note: max limits for inclusion and consistency proofs are not tested. -// Note: TreeHeadV1 extensions are not tested (not used by stfe) -func TestEncDecStItem(t *testing.T) { - logIdSize := 35 - signatureMin := 1 - signatureMax := 65535 - messageMax := 65535 - nodeHashMin := 32 - nodeHashMax := 255 - packageMin := 1 - packageMax := 255 - checksumMin := 1 - checksumMax := 64 - for _, table := range []struct { - description string - item *StItem - wantErr bool - }{ - // signed_tree_head_v1 - { - description: "too short log id", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), make([]byte, logIdSize-1), testSignature), - wantErr: true, - }, - { - description: "too large log id", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), make([]byte, logIdSize+1), testSignature), - wantErr: true, - }, - { - description: "ok log id: min and max", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature), - }, - { - description: "too short signature", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, make([]byte, signatureMin-1)), - wantErr: true, - }, - { - description: "too large signature", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, make([]byte, signatureMax+1)), - wantErr: true, - }, - { - description: "ok signature: min", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, make([]byte, signatureMin)), - }, - { - description: "ok signature: max", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, make([]byte, signatureMax)), - }, - { - description: "too short root hash", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMin-1))), testLogId, testSignature), - wantErr: true, - }, - { - description: "too large root hash", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMax+1))), testLogId, testSignature), - wantErr: true, - }, - { - description: "ok root hash: min", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMin))), testLogId, testSignature), - }, - { - description: "ok root hash: min", - item: NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMax))), testLogId, testSignature), - }, - // signed_debug_info_v1 - { - description: "too short log id", - item: NewSignedDebugInfoV1(make([]byte, logIdSize-1), testMessage, testSignature), - wantErr: true, - }, - { - description: "too large log id", - item: NewSignedDebugInfoV1(make([]byte, logIdSize+1), testMessage, testSignature), - wantErr: true, - }, - { - description: "ok log id: min and max", - item: NewSignedDebugInfoV1(testLogId, testMessage, testSignature), - }, - { - description: "too large message", - item: NewSignedDebugInfoV1(testLogId, make([]byte, messageMax+1), testSignature), - wantErr: true, - }, - { - description: "ok message: max", - item: NewSignedDebugInfoV1(testLogId, make([]byte, messageMax), testSignature), - }, - { - description: "too short signature", - item: NewSignedDebugInfoV1(testLogId, testMessage, make([]byte, signatureMin-1)), - wantErr: true, - }, - { - description: "too large signature", - item: NewSignedDebugInfoV1(testLogId, testMessage, make([]byte, signatureMax+1)), - wantErr: true, - }, - { - description: "ok signature: min", - item: NewSignedDebugInfoV1(testLogId, testMessage, make([]byte, signatureMin)), - }, - { - description: "ok signature: max", - item: NewSignedDebugInfoV1(testLogId, testMessage, make([]byte, signatureMax)), - }, - // consistency_proof_v1 - { - description: "too short log id", - item: NewConsistencyProofV1(make([]byte, logIdSize-1), testTreeSize, testTreeSizeLarger, testProof), - wantErr: true, - }, - { - description: "too large log id", - item: NewConsistencyProofV1(make([]byte, logIdSize+1), testTreeSize, testTreeSizeLarger, testProof), - wantErr: true, - }, - { - description: "ok log id: min and max", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, testProof), - }, - { - description: "too small node hash in proof", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, [][]byte{make([]byte, nodeHashMin-1)}), - wantErr: true, - }, - { - description: "too large node hash in proof", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, [][]byte{make([]byte, nodeHashMax+1)}), - wantErr: true, - }, - { - description: "ok proof: min node hash", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, [][]byte{make([]byte, nodeHashMin)}), - }, - { - description: "ok proof: max node hash", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, [][]byte{make([]byte, nodeHashMin)}), - }, - { - description: "ok proof: empty", - item: NewConsistencyProofV1(testLogId, testTreeSize, testTreeSizeLarger, [][]byte{}), - }, - // inclusion_proof_v1 - { - description: "too short log id", - item: NewInclusionProofV1(make([]byte, logIdSize-1), testTreeSize, testIndex, testProof), - wantErr: true, - }, - { - description: "too large log id", - item: NewInclusionProofV1(make([]byte, logIdSize+1), testTreeSize, testIndex, testProof), - wantErr: true, - }, - { - description: "ok log id: min and max", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, testProof), - }, - { - description: "too short node hash in proof", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, [][]byte{make([]byte, nodeHashMin-1)}), - wantErr: true, - }, - { - description: "too large node hash in proof", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, [][]byte{make([]byte, nodeHashMax+1)}), - wantErr: true, - }, - { - description: "ok proof: min node hash", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, [][]byte{make([]byte, nodeHashMin)}), - }, - { - description: "ok proof: max node hash", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, [][]byte{make([]byte, nodeHashMax)}), - }, - { - description: "ok proof: empty", - item: NewInclusionProofV1(testLogId, testTreeSize, testIndex, [][]byte{}), - }, - // checksum_v1 - { - description: "too short package", - item: NewChecksumV1(make([]byte, packageMin-1), testChecksum, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - wantErr: true, - }, - { - description: "too large package", - item: NewChecksumV1(make([]byte, packageMax+1), testChecksum, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - wantErr: true, - }, - { - description: "ok package: min", - item: NewChecksumV1(make([]byte, packageMin), testChecksum, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - }, - { - description: "ok package: max", - item: NewChecksumV1(make([]byte, packageMax), testChecksum, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - }, - { - description: "too short checksum", - item: NewChecksumV1(testPackage, make([]byte, checksumMin-1), mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - wantErr: true, - }, - { - description: "too large checksum", - item: NewChecksumV1(testPackage, make([]byte, checksumMax+1), mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - wantErr: true, - }, - { - description: "ok checksum: min", - item: NewChecksumV1(testPackage, make([]byte, checksumMin), mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - }, - { - description: "ok checksum: max", - item: NewChecksumV1(testPackage, make([]byte, checksumMax), mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)), - }, // namespace (un)marshal is already tested in its own package (skip) - { - description: "ok cosigned sth", - item: NewCosignedTreeHeadV1( - NewSignedTreeHeadV1(NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, testNodeHash)), testLogId, testSignature).SignedTreeHeadV1, - []SignatureV1{ - SignatureV1{ - *mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk), - testSignature, - }, - }, - ), - }, // TODO: the only thing that is not tested elsewhere for cosigned sth is bound on signature. Unify signature into a type => some tests go away. - } { - b, err := table.item.MarshalB64() - if err != nil && !table.wantErr { - t.Errorf("failed marshaling StItem(%s) in test %q: %v", table.item.Format, table.description, err) - } else if err == nil && table.wantErr { - t.Errorf("succeeded marshaling StItem(%s) in test %q but want failure", table.item.Format, table.description) - } - if err != nil || table.wantErr { - continue // nothing to unmarshal - } - - var item StItem - if err := item.UnmarshalB64(b); err != nil { - t.Errorf("failed unmarshaling StItem(%s) in test %q: %v", table.item.Format, table.description, err) - } - } -} - -// TestTreeHeadMarshal tests that valid tree heads can be marshaled and that -// invalid ones cannot. -// -// Note: TreeHeadV1 extensions are not tested (not used by stfe) -func TestTreeHeadMarshal(t *testing.T) { - nodeHashMin := 32 - nodeHashMax := 255 - for _, table := range []struct { - description string - th *TreeHeadV1 - wantErr bool - }{ - { - description: "too short root hash", - th: NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMin-1))), - wantErr: true, - }, - { - description: "too large root hash", - th: NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMax+1))), - wantErr: true, - }, - { - description: "ok tree head: min node hash", - th: NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMin))), - }, - { - description: "ok tree head: max node hash", - th: NewTreeHeadV1(makeTrillianLogRoot(t, testTimestamp, testTreeSize, make([]byte, nodeHashMax))), - }, - } { - if _, err := table.th.Marshal(); err != nil && !table.wantErr { - t.Errorf("failed marshaling in test %q: %v", table.description, err) - } else if err == nil && table.wantErr { - t.Errorf("succeeded marshaling but wanted error in test %q: %v", table.description, err) - } - } -} - -// TestStItemUnmarshal tests that invalid ST items cannot be unmarshaled -func TestStItemUnmarshalFailure(t *testing.T) { - b, err := NewChecksumV1(testPackage, testChecksum, mustNewNamespaceEd25519V1(t, testdata.Ed25519Vk)).Marshal() - if err != nil { - t.Errorf("must marshal ChecksumV1 StItem: %v", err) - return - } - - var checksum StItem - if err := checksum.Unmarshal(append(b[:], []byte{0}...)); err == nil { - t.Errorf("succeeded unmarshaling but wanted error: one extra byte") - } - if err := checksum.Unmarshal(append(b[:], b[:]...)); err == nil { - t.Errorf("succeeded unmarshaling but wanted error: one extra b") - } - if err := checksum.Unmarshal([]byte{0}); err == nil { - t.Errorf("succeeded unmarshaling but wanted error: just a single byte") - } - - b[0] = byte(len(testPackage)) + 1 // will mess up the first length specifier - if err := checksum.Unmarshal(b); err == nil { - t.Errorf("succeeded unmarshaling but wanted error: bad length") - } - - if err := checksum.UnmarshalB64("@" + b64(b[1:])); err == nil { - t.Errorf("succeded unmarshaling base64 but wanted error: bad byte") - } -} diff --git a/types/namespace_test.go b/types/namespace_test.go index c38d295..a5847ef 100644 --- a/types/namespace_test.go +++ b/types/namespace_test.go @@ -146,8 +146,7 @@ func test_cases_verify(t *testing.T) []testCaseNamespace { // test_cases_verify_ed25519v1 returns ed25519_v1 Namespace.Verify() tests func test_cases_verify_ed25519v1(t *testing.T) []testCaseNamespace { testEd25519Sk := [64]byte{230, 122, 195, 152, 194, 195, 147, 153, 80, 120, 153, 79, 102, 27, 52, 187, 136, 218, 150, 234, 107, 9, 167, 4, 92, 21, 11, 113, 42, 29, 129, 69, 75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} - testEd25519Vk := [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, - 37, 45, 210} + testEd25519Vk := [32]byte{75, 60, 249, 150, 229, 93, 75, 32, 103, 126, 244, 37, 53, 182, 68, 82, 249, 109, 49, 94, 10, 19, 146, 244, 58, 191, 169, 107, 78, 37, 45, 210} return []testCaseNamespace{ { description: "test_cases_verify_ed25519v1: invalid: sk signed message, but vk is not for sk", diff --git a/types/stitem.go b/types/stitem.go index b214082..447cad0 100644 --- a/types/stitem.go +++ b/types/stitem.go @@ -119,3 +119,74 @@ func (i StItem) String() string { return fmt.Sprintf("unknown StItem: %v", i.Format) } } + +func NewSignedTreeHeadV1(th *TreeHeadV1, sig *SignatureV1) *StItem { + return &StItem{ + Format: StFormatSignedTreeHeadV1, + SignedTreeHeadV1: &SignedTreeHeadV1{ + TreeHead: *th, + Signature: *sig, + }, + } +} + +func NewCosignedTreeHeadV1(sth *SignedTreeHeadV1, cosig []SignatureV1) *StItem { + if cosig == nil { + cosig = make([]SignatureV1, 0) + } + return &StItem{ + Format: StFormatCosignedTreeHeadV1, + CosignedTreeHeadV1: &CosignedTreeHeadV1{ + SignedTreeHead: *sth, + Cosignatures: cosig, + }, + } +} + +func NewConsistencyProofV1(id *Namespace, size1, size2 uint64, path []NodeHash) *StItem { + return &StItem{ + Format: StFormatConsistencyProofV1, + ConsistencyProofV1: &ConsistencyProofV1{ + LogId: *id, + TreeSize1: size1, + TreeSize2: size2, + ConsistencyPath: path, + }, + } +} + +func NewInclusionProofV1(id *Namespace, size, index uint64, path []NodeHash) *StItem { + return &StItem{ + Format: StFormatInclusionProofV1, + InclusionProofV1: &InclusionProofV1{ + LogId: *id, + TreeSize: size, + LeafIndex: index, + InclusionPath: path, + }, + } +} + +func NewSignedChecksumV1(data *ChecksumV1, sig *SignatureV1) *StItem { + return &StItem{ + Format: StFormatSignedChecksumV1, + SignedChecksumV1: &SignedChecksumV1{ + Data: *data, + Signature: *sig, + }, + } +} + +func NewTreeHeadV1(timestamp, size uint64, hash, extension []byte) *TreeHeadV1 { + if extension == nil { + extension = make([]byte, 0) + } + return &TreeHeadV1{ + Timestamp: timestamp, + TreeSize: size, + RootHash: NodeHash{ + Data: hash, + }, + Extension: extension, + } +} diff --git a/types/stitem_test.go b/types/stitem_test.go index c6e413a..90d6808 100644 --- a/types/stitem_test.go +++ b/types/stitem_test.go @@ -38,3 +38,27 @@ func TestStItemString(t *testing.T) { } } } + +// TODO: TestNewSignedTreeHeadV1 +func TestNewSignedTreeHeadV1(t *testing.T) { +} + +// TODO: TestNewCosignedTreeHeadV1 +func TestNewCosignedTreeHeadV1(t *testing.T) { +} + +// TODO: TestNewConsistencyProofV1 +func TestNewConsistencyProofV1(t *testing.T) { +} + +// TODO: TestNewInclusionProofV1 +func TestNewInclusionProofV1(t *testing.T) { +} + +// TODO: TestNewSignedChecksumV1 +func TestNewSignedChecksumV1(t *testing.T) { +} + +// TODO: TestNewTreeHeadV1 +func TestNewTreeHeadV1(t *testing.T) { +} diff --git a/util.go b/util.go new file mode 100644 index 0000000..847c3f7 --- /dev/null +++ b/util.go @@ -0,0 +1,40 @@ +package stfe + +import ( + "fmt" + + "github.com/google/trillian" + ttypes "github.com/google/trillian/types" + "github.com/system-transparency/stfe/types" +) + +func NewTreeHeadV1FromLogRoot(lr *ttypes.LogRootV1) *types.TreeHeadV1 { + return &types.TreeHeadV1{ + Timestamp: uint64(lr.TimestampNanos / 1000 / 1000), + TreeSize: uint64(lr.TreeSize), + RootHash: types.NodeHash{ + Data: lr.RootHash, + }, + Extension: make([]byte, 0), + } +} + +func NewNodePathFromHashPath(hashes [][]byte) []types.NodeHash { + path := make([]types.NodeHash, 0, len(hashes)) + for _, hash := range hashes { + path = append(path, types.NodeHash{hash}) + } + return path +} + +func NewStItemListFromLeaves(leaves []*trillian.LogLeaf) (*types.StItemList, error) { + items := make([]types.StItem, 0, len(leaves)) + for _, leaf := range leaves { + var item types.StItem + if err := types.Unmarshal(leaf.LeafValue, &item); err != nil { + return nil, fmt.Errorf("Unmarshal failed: %v", err) + } + items = append(items, item) + } + return &types.StItemList{items}, nil +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..b40a672 --- /dev/null +++ b/util_test.go @@ -0,0 +1,17 @@ +package stfe + +import ( + "testing" +) + +// TODO: TestNewTreeHeadV1FromLogRoot +func TestNewTreeHeadV1FromLogRoot(t *testing.T) { +} + +// TODO: TestNewNodePathFromHashPath +func TestNewNodePathFromHashPath(t *testing.T) { +} + +// TODO: TestStItemListFromLeaves +func TestStItemListFromLeaves(t *testing.T) { +} -- cgit v1.2.3