diff options
| author | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2020-11-23 21:36:00 +0100 | 
|---|---|---|
| committer | Rasmus Dahlberg <rasmus.dahlberg@kau.se> | 2020-11-23 21:36:00 +0100 | 
| commit | b2e10535ef094e8bc9995769c2a22a1ee29cbe57 (patch) | |
| tree | adce4aaef9978a04e09d24499f99752c6d2dbf8c | |
| parent | 15444f91e74de6e1ca8ea99511e8cb6dcd92fa91 (diff) | |
added start on isolated trillian checks
| -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)  	} | 
