diff options
-rw-r--r-- | handler.go | 8 | ||||
-rw-r--r-- | handler_test.go | 85 | ||||
-rw-r--r-- | server/testdata/data.go | 46 | ||||
-rw-r--r-- | server/testdata/helper.go | 47 | ||||
-rw-r--r-- | server/testdata/type.go | 32 | ||||
-rw-r--r-- | trillian.go | 16 | ||||
-rw-r--r-- | trillian_test.go | 214 | ||||
-rw-r--r-- | type_test.go | 6 |
8 files changed, 265 insertions, 189 deletions
@@ -65,8 +65,8 @@ func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.R ExtraData: appendix, }, }) - if status, errInner := checkQueueLeaf(trsp, err); errInner != nil { - return status, fmt.Errorf("bad QueueLeafResponse: %v", errInner) + 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) @@ -182,8 +182,8 @@ func getSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Req LogId: i.LogParameters.TreeId, }) var lr types.LogRootV1 - if status, errInner := checkGetLatestSignedLogRoot(i.LogParameters, trsp, err, &lr); errInner != nil { - return status, fmt.Errorf("bad GetLatestSignedLogRootResponse: %v", errInner) + if errInner := checkGetLatestSignedLogRoot(i.LogParameters, trsp, err, &lr); errInner != nil { + return http.StatusInternalServerError, fmt.Errorf("bad GetLatestSignedLogRootResponse: %v", errInner) } sth, err := i.LogParameters.genV1Sth(NewTreeHeadV1(&lr)) diff --git a/handler_test.go b/handler_test.go index 277c74f..4496b15 100644 --- a/handler_test.go +++ b/handler_test.go @@ -2,6 +2,7 @@ package stfe import ( "bytes" + "context" "crypto" "fmt" "strings" @@ -20,7 +21,7 @@ import ( "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/server/testdata" + "github.com/system-transparency/stfe/testdata" "github.com/system-transparency/stfe/x509util" ) @@ -197,7 +198,7 @@ func TestGetEntries(t *testing.T) { Start: 0, End: 1, }, - trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, false), + trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, false), wantCode: http.StatusInternalServerError, wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", }, @@ -207,7 +208,7 @@ func TestGetEntries(t *testing.T) { Start: 0, End: 1, }, - trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, true), + trsp: makeTrillianGetLeavesByRangeResponse(t, 0, 1, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, true), wantCode: http.StatusOK, }, } { @@ -226,7 +227,7 @@ func TestGetEntries(t *testing.T) { req.URL.RawQuery = q.Encode() if table.trsp != nil || table.terr != nil { - th.client.EXPECT().GetLeavesByRange(testdata.NewDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + th.client.EXPECT().GetLeavesByRange(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) } w := httptest.NewRecorder() th.getHandler(t, "get-entries").ServeHTTP(w, req) @@ -298,29 +299,29 @@ func TestAddEntry(t *testing.T) { }{ { description: "empty trillian response", - breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, true), + breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, true), terr: fmt.Errorf("back-end failure"), wantCode: http.StatusInternalServerError, wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", }, { description: "bad request parameters", - breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, false), + breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, false), wantCode: http.StatusBadRequest, wantErrText: http.StatusText(http.StatusBadRequest) + "\n", }, { description: "log signature failure", - breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, true), - trsp: makeTrillianQueueLeafResponse(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey), + breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, true), + trsp: makeTrillianQueueLeafResponse(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, 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: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey, true), - trsp: makeTrillianQueueLeafResponse(t, []byte("foobar-1.2.3"), testdata.PemChain, testdata.PemChainKey), + breq: makeTestLeafBuffer(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, true), + trsp: makeTrillianQueueLeafResponse(t, []byte("foobar-1.2.3"), testdata.FirstPemChain, testdata.FirstPemChainKey, false), wantCode: http.StatusOK, signer: cttestdata.NewSignerWithFixedSig(nil, make([]byte, 32)), }, @@ -338,7 +339,7 @@ func TestAddEntry(t *testing.T) { if table.trsp != nil || table.terr != nil { // TODO: replace gomock.Any with a check that leaf and appendix are OK, e.g., chain length should be 3 - th.client.EXPECT().QueueLeaf(testdata.NewDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + th.client.EXPECT().QueueLeaf(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) } w := httptest.NewRecorder() th.postHandler(t, "add-entry").ServeHTTP(w, req) @@ -382,8 +383,9 @@ func TestAddEntry(t *testing.T) { } } -// TestGetSth: docdoc and TODO: move quirky tests to trillian_tests.go? 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 @@ -399,45 +401,22 @@ func TestGetSth(t *testing.T) { wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", }, { - description: "incomplete trillian response: nil response", - wantCode: http.StatusInternalServerError, - wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", - }, - { - description: "incomplete trillian response: no signed log root", - trsp: &trillian.GetLatestSignedLogRootResponse{SignedLogRoot: nil}, - wantCode: http.StatusInternalServerError, - wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", - }, - { - description: "incomplete trillian response: truncated log root", - trsp: testdata.TruncatedSignedLogRootResponse(t), - wantCode: http.StatusInternalServerError, - wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", - }, - { - description: "incomplete trillian response: invalid root hash size", - trsp: testdata.NewGetLatestSignedLogRootResponse(t, 0, 0, make([]byte, 31)), - wantCode: http.StatusInternalServerError, - wantErrText: http.StatusText(http.StatusInternalServerError) + "\n", - }, - { description: "marshal failure: no signature", - trsp: testdata.NewGetLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)), + 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: testdata.NewGetLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)), + 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: testdata.NewGetLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)), + trsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)), wantCode: http.StatusOK, signer: cttestdata.NewSignerWithFixedSig(nil, make([]byte, 32)), }, @@ -453,7 +432,7 @@ func TestGetSth(t *testing.T) { } w := httptest.NewRecorder() - th.client.EXPECT().GetLatestSignedLogRoot(testdata.NewDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + th.client.EXPECT().GetLatestSignedLogRoot(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) th.getHandler(t, "get-sth").ServeHTTP(w, req) if w.Code != table.wantCode { t.Errorf("GET(%s)=%d, want http status code %d", url, w.Code, table.wantCode) @@ -562,7 +541,7 @@ func TestGetConsistencyProof(t *testing.T) { w := httptest.NewRecorder() if table.trsp != nil || table.terr != nil { - th.client.EXPECT().GetConsistencyProof(testdata.NewDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + th.client.EXPECT().GetConsistencyProof(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) } th.getHandler(t, "get-consistency-proof").ServeHTTP(w, req) if w.Code != table.wantCode { @@ -671,7 +650,7 @@ func TestGetProofByHash(t *testing.T) { w := httptest.NewRecorder() if table.trsp != nil || table.terr != nil { - th.client.EXPECT().GetInclusionProofByHash(testdata.NewDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) + th.client.EXPECT().GetInclusionProofByHash(newDeadlineMatcher(), gomock.Any()).Return(table.trsp, table.terr) } th.getHandler(t, "get-proof-by-hash").ServeHTTP(w, req) if w.Code != table.wantCode { @@ -773,3 +752,27 @@ func makeTestLeafBuffer(t *testing.T, name, pemChain, pemKey []byte, valid bool) } 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/server/testdata/data.go b/server/testdata/data.go deleted file mode 100644 index 3c814d9..0000000 --- a/server/testdata/data.go +++ /dev/null @@ -1,46 +0,0 @@ -package testdata - -var ( - // PemAnchors is a list of trusted root certificates - PemAnchors = []byte(`-----BEGIN CERTIFICATE----- -MIIB/TCCAa+gAwIBAgIUDYJzaC5VSkKwiLVAxO5MyphAkN8wBQYDK2VwMGwxCzAJ -BgNVBAYTAk5BMQswCQYDVQQIDAJOQTELMAkGA1UEBwwCTkExCzAJBgNVBAoMAk5B -MQswCQYDVQQLDAJOQTEWMBQGA1UEAwwNc3RmZSB0ZXN0ZGF0YTERMA8GCSqGSIb3 -DQEJARYCTkEwHhcNMjAxMTAzMTgzMTMxWhcNMzIwMTIxMTgzMTMxWjBsMQswCQYD -VQQGEwJOQTELMAkGA1UECAwCTkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTEL -MAkGA1UECwwCTkExFjAUBgNVBAMMDXN0ZmUgdGVzdGRhdGExETAPBgkqhkiG9w0B -CQEWAk5BMCowBQYDK2VwAyEAJ1IiXCB4YHwdWka9MM0bc7LvKAtksmtIo8IhkuEB -uzGjYzBhMB0GA1UdDgQWBBQBvsxROtKU6zmr/SxcfTMDsAQcMTAfBgNVHSMEGDAW -gBQBvsxROtKU6zmr/SxcfTMDsAQcMTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB -/wQEAwIChDAFBgMrZXADQQCXh6kDnE5giTjcLET2S94qTwnHVAj57DJcR/rf9Jy8 -NMGbtzTL0/V0B8DHuJFA/islbZJbN7rSvqddEKL8N2gI ------END CERTIFICATE-----`) - // PemChain is composed of an end-entity and intermediate certificate - PemChain = []byte(`-----BEGIN CERTIFICATE----- -MIIBbDCCAR4CFDfeuu6XURfn7AE4WShuwZBHEaLIMAUGAytlcDBsMQswCQYDVQQG -EwJOQTELMAkGA1UECAwCTkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTELMAkG -A1UECwwCTkExFjAUBgNVBAMMDXN0ZmUgdGVzdGRhdGExETAPBgkqhkiG9w0BCQEW -Ak5BMB4XDTIwMTEwMzE4MzI0MFoXDTMyMDEyMTE4MzI0MFowRTELMAkGA1UEBhMC -QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAqMAUGAytlcAMhAJvk390ZvwULplBri03Od4LLz+Sf/OUHu+20 -wik+T9y5MAUGAytlcANBANekliXq4ttoClBJDZoktIQxyHHNcWyXFrj1HlOaT5bC -I3GIqqZ60Ua3jKytnEsKsD2rLMPItDwmG6wYSecy2ws= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIB7jCCAaCgAwIBAgICEAAwBQYDK2VwMGwxCzAJBgNVBAYTAk5BMQswCQYDVQQI -DAJOQTELMAkGA1UEBwwCTkExCzAJBgNVBAoMAk5BMQswCQYDVQQLDAJOQTEWMBQG -A1UEAwwNc3RmZSB0ZXN0ZGF0YTERMA8GCSqGSIb3DQEJARYCTkEwHhcNMjAxMTAz -MTgzMjE4WhcNMzIwMTIxMTgzMjE4WjBsMQswCQYDVQQGEwJOQTELMAkGA1UECAwC -TkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTELMAkGA1UECwwCTkExFjAUBgNV -BAMMDXN0ZmUgdGVzdGRhdGExETAPBgkqhkiG9w0BCQEWAk5BMCowBQYDK2VwAyEA -F1yPPpjHKDAKN73pBFGXzAvIjdkLLimydu2y1HLMOiKjZjBkMB0GA1UdDgQWBBQ6 -P7JQ7yXtrTh7YkVU0I78P9A+nDAfBgNVHSMEGDAWgBQBvsxROtKU6zmr/SxcfTMD -sAQcMTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIChDAFBgMrZXAD -QQBm1GMV0ADPnXRWnelCW9tcyTh0p9hKefuSy/MNx7/XLHKnM5fX+yHqD84QOxES -Vc510vi4dM8I+e/vcoBsmMQP ------END CERTIFICATE-----`) - // PemChainKey is the private key of the final certificate in PemChain - PemChainKey = []byte(`-----BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIDme3WaCwW2/FX095yh02yIIsn0D3vbvN5NsJzcdUwq1 ------END PRIVATE KEY-----`) -) diff --git a/server/testdata/helper.go b/server/testdata/helper.go deleted file mode 100644 index 6874616..0000000 --- a/server/testdata/helper.go +++ /dev/null @@ -1,47 +0,0 @@ -package testdata - -import ( - "testing" - - "github.com/google/trillian" - "github.com/google/trillian/types" -) - -// NewGetLatestSignedLogRootResponse creates a new trillian STH. Revision, -// Metadata, Proof, KeyHint, and LogRootSignature are unsset. -func NewGetLatestSignedLogRootResponse(t *testing.T, timestamp, size uint64, hash []byte) *trillian.GetLatestSignedLogRootResponse { - t.Helper() - return &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: marshalSignedLogRoot(t, &types.LogRootV1{ - TreeSize: size, - RootHash: hash, - TimestampNanos: timestamp, - Revision: 0, // not used by stfe - Metadata: nil, // not used by stfe - }), - Proof: nil, // not used by stfe - } -} - -// TruncatedSignedLogRootResponse creates a truncated signed log root response -// that cannot be unmarshalled, i.e., SignedLogRoot.LogRoot is invalid. -func TruncatedSignedLogRootResponse(t *testing.T) *trillian.GetLatestSignedLogRootResponse { - t.Helper() - slrr := NewGetLatestSignedLogRootResponse(t, 0, 0, make([]byte, 32)) - slrr.SignedLogRoot.LogRoot = slrr.SignedLogRoot.LogRoot[1:] - return slrr -} - -// marshalSignedLogRoot must marshal a signed log root -func marshalSignedLogRoot(t *testing.T, lr *types.LogRootV1) *trillian.SignedLogRoot { - t.Helper() - rootBytes, err := lr.MarshalBinary() - if err != nil { - t.Fatalf("failed to marshal root in test: %v", err) - } - return &trillian.SignedLogRoot{ - KeyHint: nil, // not used by stfe - LogRoot: rootBytes, - LogRootSignature: nil, // not used by stfe - } -} diff --git a/server/testdata/type.go b/server/testdata/type.go deleted file mode 100644 index 93041c9..0000000 --- a/server/testdata/type.go +++ /dev/null @@ -1,32 +0,0 @@ -package testdata - -import ( - "context" - "fmt" - - "github.com/golang/mock/gomock" -) - -// 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/trillian.go b/trillian.go index 8ae96a1..5fab6f5 100644 --- a/trillian.go +++ b/trillian.go @@ -11,15 +11,15 @@ import ( "google.golang.org/grpc/codes" ) -func checkQueueLeaf(rsp *trillian.QueueLeafResponse, err error) (int, error) { +func checkQueueLeaf(rsp *trillian.QueueLeafResponse, err error) error { if err != nil || rsp == nil || rsp.QueuedLeaf == nil { - return http.StatusInternalServerError, fmt.Errorf("%v", err) + return fmt.Errorf("%v", err) } if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { // no need to report this as an invalid request, just (re)issue sdi glog.V(3).Infof("queued leaf is a duplicate => %X", rsp.QueuedLeaf.Leaf.LeafValue) } - return 0, nil + return nil } func checkGetLeavesByRange(req *GetEntriesRequest, rsp *trillian.GetLeavesByRangeResponse, err error) (int, error) { @@ -62,17 +62,17 @@ func checkGetConsistencyProof(lp *LogParameters, rsp *trillian.GetConsistencyPro return checkHashPath(lp.HashType.Size(), rsp.Proof.Hashes) } -func checkGetLatestSignedLogRoot(lp *LogParameters, rsp *trillian.GetLatestSignedLogRootResponse, err error, out *types.LogRootV1) (int, error) { +func checkGetLatestSignedLogRoot(lp *LogParameters, rsp *trillian.GetLatestSignedLogRootResponse, err error, out *types.LogRootV1) error { if err != nil || rsp == nil || rsp.SignedLogRoot == nil || rsp.SignedLogRoot.LogRoot == nil { - return http.StatusInternalServerError, fmt.Errorf("%v", err) + return fmt.Errorf("%v", err) } if err := out.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 len(out.RootHash) != lp.HashType.Size() { - return http.StatusInternalServerError, fmt.Errorf("invalid root hash: %v", out.RootHash) + return fmt.Errorf("invalid root hash: %v", out.RootHash) } - return 0, nil + return nil } func checkHashPath(hashSize int, path [][]byte) (int, error) { diff --git a/trillian_test.go b/trillian_test.go index 174fa13..6c9b7af 100644 --- a/trillian_test.go +++ b/trillian_test.go @@ -6,18 +6,139 @@ import ( "github.com/google/trillian" "github.com/google/trillian/types" - "github.com/system-transparency/stfe/server/testdata" + "github.com/system-transparency/stfe/testdata" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -// TODO: TestCheckQueueLeaf func TestCheckQueueLeaf(t *testing.T) { + for _, table := range []struct { + description string + rsp *trillian.QueueLeafResponse + err error + wantErr bool + }{ + { + description: "trillian error", + err: fmt.Errorf("backend error"), + wantErr: true, + }, + { + description: "empty trillian response", + wantErr: true, + }, + { + description: "partial trillian response: empty QueuedLeaf field", + rsp: &trillian.QueueLeafResponse{}, + wantErr: true, + }, + { + description: "ok: duplicate leaf", + rsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true), + }, + { + description: "ok: new leaf", + rsp: makeTrillianQueueLeafResponse(t, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, 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) + } + } } -// TODO: TestCheckGetLeavesByRange func TestCheckGetLeavesByRange(t *testing.T) { + // rsp without leaves + noLeaves := makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true) + noLeaves.Leaves = nil + + // rsp without signed log root + noSlr := makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true) + noSlr.SignedLogRoot = nil + + // rsp without log root + noLr := makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true) + noLr.SignedLogRoot.LogRoot = nil + + // rsp with root that cannot be unmarshalled + tr := makeTrillianGetLeavesByRangeResponse(t, 0, 1, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true) + tr.SignedLogRoot.LogRoot = tr.SignedLogRoot.LogRoot[1:] + + // rsp with fixed tree size + fixedSize := makeTrillianGetLeavesByRangeResponse(t, int64(testTreeSize)-1, int64(testTreeSize)-1, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true) + fixedSize.SignedLogRoot = makeLatestSignedLogRootResponse(t, 0, testTreeSize, testNodeHash).SignedLogRoot + + for _, table := range []struct { + description string + req *GetEntriesRequest + rsp *trillian.GetLeavesByRangeResponse + err error + wantErr bool + }{ + { + description: "trillian error", + err: fmt.Errorf("backend error"), + wantErr: true, + }, + { + description: "empty trillian response", + wantErr: true, + }, + { + description: "partial trillian response: no leaves", + rsp: noLeaves, + wantErr: true, + }, + { + description: "partial trillian response: no signed log root", + rsp: noSlr, + wantErr: true, + }, + { + description: "partial trillian response: no log root", + rsp: noLr, + wantErr: true, + }, + { + description: "bad response: too many leaves", + req: &GetEntriesRequest{Start: 0, End: 1}, + rsp: makeTrillianGetLeavesByRangeResponse(t, 0, 2, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true), + wantErr: true, + }, + { + description: "bad response: too many leaves", + req: &GetEntriesRequest{Start: 0, End: 1}, + rsp: tr, + wantErr: true, + }, + { + description: "bad response: start is not a valid index", + req: &GetEntriesRequest{Start: int64(testTreeSize), End: int64(testTreeSize)}, + rsp: fixedSize, + wantErr: true, + }, + { + description: "ok response: interval refers to the latest leaf", + req: &GetEntriesRequest{Start: int64(testTreeSize) - 1, End: int64(testTreeSize) - 1}, + rsp: fixedSize, + }, + { + description: "bad response: invalid leaf indices", + req: &GetEntriesRequest{Start: 10, End: 11}, + rsp: makeTrillianGetLeavesByRangeResponse(t, 11, 12, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true), + wantErr: true, + }, + { + description: "ok response: a bunch of leaves", + req: &GetEntriesRequest{Start: 10, End: 20}, + rsp: makeTrillianGetLeavesByRangeResponse(t, 10, 20, testPackage, testdata.FirstPemChain, testdata.FirstPemChainKey, true), + }, + } { + 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) + } + } } // TODO: TestCheckGetInclusionProofByHash @@ -28,8 +149,61 @@ func TestCheckGetInclusionProofByHash(t *testing.T) { func TestCheckGetConsistencyProof(t *testing.T) { } -// TODO: TestCheckGetLatestSignedLogRoot func TestCheckGetLatestSignedLogRoot(t *testing.T) { + // response with no log root + noLr := makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash) + noLr.SignedLogRoot.LogRoot = nil + + // response with truncated log root + tr := makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash) + tr.SignedLogRoot.LogRoot = tr.SignedLogRoot.LogRoot[1:] + + lp := makeTestLogParameters(t, nil) + for _, table := range []struct { + description string + rsp *trillian.GetLatestSignedLogRootResponse + err error + wantErr bool + }{ + { + description: "bad trillian response: error", + err: fmt.Errorf("backend failure"), + wantErr: true, + }, + { + description: "bad trillian response: empty", + wantErr: true, + }, + { + description: "bad trillian response: no signed log root", + rsp: &trillian.GetLatestSignedLogRootResponse{SignedLogRoot: nil}, + wantErr: true, + }, + { + description: "bad trillian response: no log root", + rsp: noLr, + wantErr: true, + }, + { + description: "bad trillian response: truncated log root", + rsp: tr, + wantErr: true, + }, + { + description: "bad trillian response: invalid root hash size", + rsp: makeLatestSignedLogRootResponse(t, 0, 0, make([]byte, 31)), + wantErr: true, + }, + { + description: "ok response", + rsp: makeLatestSignedLogRootResponse(t, 0, 0, testNodeHash), + }, + } { + 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) + } + } } // makeTrillianQueueLeafResponse creates a valid trillian QueueLeafResponse @@ -37,9 +211,13 @@ func TestCheckGetLatestSignedLogRoot(t *testing.T) { // is a PEM-encoded ed25519 signing key, and pemChain its certificate chain. // // Note: MerkleLeafHash and LeafIdentityHash are unset (not used by stfe). -func makeTrillianQueueLeafResponse(t *testing.T, name, pemChain, pemKey []byte) *trillian.QueueLeafResponse { +func makeTrillianQueueLeafResponse(t *testing.T, name, pemChain, pemKey []byte, dupCode bool) *trillian.QueueLeafResponse { t.Helper() leaf, appendix := makeTestLeaf(t, name, pemChain, pemKey) + 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{ @@ -49,7 +227,7 @@ func makeTrillianQueueLeafResponse(t *testing.T, name, pemChain, pemKey []byte) LeafIndex: 0, // not applicable (log is not pre-ordered) LeafIdentityHash: nil, // not used by stfe }, - Status: status.New(codes.OK, "ok").Proto(), + Status: s, }, } } @@ -94,7 +272,7 @@ func makeTrillianGetConsistencyProofResponse(t *testing.T, path [][]byte) *trill // Note: MerkleLeafHash and LeafIdentityHash are unset (not used by stfe). func makeTrillianGetLeavesByRangeResponse(t *testing.T, start, end int64, name, pemChain, pemKey []byte, valid bool) *trillian.GetLeavesByRangeResponse { t.Helper() - leaves := make([]*trillian.LogLeaf, 0, start-end+1) + leaves := make([]*trillian.LogLeaf, 0, end-start+1) for i, n := start, end+1; i < n; i++ { leaf, appendix := makeTestLeaf(t, append(name, []byte(fmt.Sprintf("_%d", i))...), pemChain, pemKey) if !valid { @@ -110,11 +288,13 @@ func makeTrillianGetLeavesByRangeResponse(t *testing.T, start, end int64, name, } return &trillian.GetLeavesByRangeResponse{ Leaves: leaves, - SignedLogRoot: testdata.NewGetLatestSignedLogRootResponse(t, 0, uint64(end)+1, make([]byte, 32)).SignedLogRoot, + 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, @@ -123,3 +303,21 @@ func makeTrillianLogRoot(t *testing.T, timestamp, size uint64, hash []byte) *typ 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_test.go b/type_test.go index 6b292ed..e249fb6 100644 --- a/type_test.go +++ b/type_test.go @@ -5,7 +5,7 @@ import ( "crypto/tls" - "github.com/system-transparency/stfe/server/testdata" + "github.com/system-transparency/stfe/testdata" "github.com/system-transparency/stfe/x509util" ) @@ -275,7 +275,7 @@ func TestEncDecStItem(t *testing.T) { // // TODO: max limits for certificate chains are not tested. func TestEncDecAppendix(t *testing.T) { - chain, err := x509util.NewCertificateList(testdata.PemChain) + chain, err := x509util.NewCertificateList(testdata.FirstPemChain) if err != nil { t.Fatalf("must decode certificate chain: %v", err) } @@ -398,7 +398,7 @@ func TestStItemUnmarshalFailure(t *testing.T) { // TestAppendixUnmarshal tests that invalid appendices cannot be unmarshaled func TestAppendixUnmarshalFailure(t *testing.T) { - chain, err := x509util.NewCertificateList(testdata.PemChain) + chain, err := x509util.NewCertificateList(testdata.FirstPemChain) if err != nil { t.Fatalf("must decode certificate chain: %v", err) } |