From 3f1489f4f4532f93f766fc849a2e3e2b65667747 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 2 Dec 2020 17:05:45 +0100 Subject: added instance and log parameter tests --- handler_test.go | 6 +- instance.go | 45 ++++++------ instance_test.go | 178 ++++++++++++++++++++++++++++++++++++++++++---- server/main.go | 2 +- x509util/testdata/data.go | 5 ++ 5 files changed, 201 insertions(+), 35 deletions(-) diff --git a/handler_test.go b/handler_test.go index 3bcc702..40fd562 100644 --- a/handler_test.go +++ b/handler_test.go @@ -23,6 +23,10 @@ import ( "github.com/system-transparency/stfe/x509util/testdata" ) +var ( + testDeadline = time.Second * 10 +) + type testHandler struct { mockCtrl *gomock.Controller client *mockclient.MockTrillianLogClient @@ -36,7 +40,7 @@ func newTestHandler(t *testing.T, signer crypto.Signer) *testHandler { mockCtrl: ctrl, client: client, instance: &Instance{ - Deadline: time.Second * 10, + Deadline: testDeadline, Client: client, LogParameters: makeTestLogParameters(t, signer), }, diff --git a/instance.go b/instance.go index b0a2d9e..122cb67 100644 --- a/instance.go +++ b/instance.go @@ -8,7 +8,6 @@ import ( "crypto/sha256" "crypto/x509" - "encoding/base64" "net/http" "github.com/google/trillian" @@ -52,8 +51,8 @@ func (i Instance) String() string { return fmt.Sprintf("%s Deadline(%v)\n", i.LogParameters, i.Deadline) } -func (p LogParameters) String() string { - return fmt.Sprintf("LogId(%s) TreeId(%d) Prefix(%s) NumAnchors(%d)", base64.StdEncoding.EncodeToString(p.LogId), p.TreeId, p.Prefix, len(p.AnchorList)) +func (lp LogParameters) String() string { + return fmt.Sprintf("LogId(%s) TreeId(%d) Prefix(%s) MaxRange(%d) MaxChain(%d) NumAnchors(%d)", lp.id(), lp.TreeId, lp.Prefix, lp.MaxRange, lp.MaxChain, len(lp.AnchorList)) } func (e Endpoint) String() string { @@ -61,7 +60,7 @@ func (e Endpoint) String() string { } // NewInstance creates a new STFE instance -func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline time.Duration, mux *http.ServeMux) *Instance { +func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline time.Duration) *Instance { return &Instance{ LogParameters: lp, Client: client, @@ -72,15 +71,21 @@ func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline // 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(treeId int64, prefix string, anchors []*x509.Certificate, signer crypto.Signer, maxRange, maxChain int64) (*LogParameters, error) { - pub, err := x509.MarshalPKIXPublicKey(signer.Public()) - if err != nil { - return nil, fmt.Errorf("failed DER encoding SubjectPublicKeyInfo: %v", err) + if signer == nil { + return nil, fmt.Errorf("need a signer but got none") + } + if len(anchors) < 1 { + return nil, fmt.Errorf("need at least one trust anchor") } if maxRange < 1 { - return nil, fmt.Errorf("invalid max range: must be at least 1") + return nil, fmt.Errorf("max range must be at least one") } if maxChain < 1 { - return nil, fmt.Errorf("invalid max chain: must be at least 1") + return nil, fmt.Errorf("max chain must be at least one") + } + pub, err := x509.MarshalPKIXPublicKey(signer.Public()) + if err != nil { + return nil, fmt.Errorf("failed DER encoding SubjectPublicKeyInfo: %v", err) } hasher := sha256.New() hasher.Write(pub) @@ -98,17 +103,6 @@ func NewLogParameters(treeId int64, prefix string, anchors []*x509.Certificate, }, nil } -// 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)), "/") -} - -// TODO: id() docdoc -func (i *LogParameters) id() string { - return base64.StdEncoding.EncodeToString(i.LogId) -} - // Handlers returns a list of STFE handlers func (i *Instance) Handlers() []Handler { return []Handler{ @@ -120,3 +114,14 @@ func (i *Instance) Handlers() []Handler { Handler{instance: i, handler: getSth, endpoint: EndpointGetSth, method: http.MethodGet}, } } + +// 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 b4b13c8..02424c6 100644 --- a/instance_test.go +++ b/instance_test.go @@ -1,11 +1,14 @@ package stfe import ( + "bytes" "testing" "crypto" + "crypto/sha256" "crypto/x509" + cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" "github.com/system-transparency/stfe/x509util" "github.com/system-transparency/stfe/x509util/testdata" ) @@ -20,22 +23,149 @@ var ( testExtKeyUsage = []x509.ExtKeyUsage{} ) -func makeTestLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { - anchorList, err := x509util.NewCertificateList(testdata.TrustAnchors) +func TestNewLogParameters(t *testing.T) { + anchors, err := x509util.NewCertificateList(testdata.TrustAnchors) if err != nil { t.Fatalf("must decode trust anchors: %v", err) } - return &LogParameters{ - LogId: testLogId, - TreeId: testTreeId, - Prefix: testPrefix, - MaxRange: testMaxRange, - MaxChain: testMaxChain, - AnchorPool: x509util.NewCertPool(anchorList), - AnchorList: anchorList, - KeyUsage: testExtKeyUsage, - Signer: signer, - HashType: testHashType, + signer, err := x509util.NewEd25519PrivateKey(testdata.LogPrivateKey) + if err != nil { + t.Fatalf("must decode private key: %v", err) + } + pub, err := x509.MarshalPKIXPublicKey(signer.Public()) + if err != nil { + t.Fatalf("must encode public key: %v", err) + } + hasher := sha256.New() + hasher.Write(pub) + logId := hasher.Sum(nil) + for _, table := range []struct { + description string + treeId int64 + prefix string + maxRange int64 + maxChain int64 + anchors []*x509.Certificate + signer crypto.Signer + wantErr bool + }{ + { + description: "invalid signer: nil", + treeId: testTreeId, + prefix: testPrefix, + maxRange: 0, + maxChain: testMaxChain, + anchors: anchors, + signer: nil, + wantErr: true, + }, + { + description: "no trust anchors", + treeId: testTreeId, + prefix: testPrefix, + maxRange: testMaxRange, + maxChain: testMaxChain, + anchors: []*x509.Certificate{}, + signer: signer, + wantErr: true, + }, + { + description: "invalid max range", + treeId: testTreeId, + prefix: testPrefix, + maxRange: 0, + maxChain: testMaxChain, + anchors: anchors, + signer: signer, + wantErr: true, + }, + { + description: "invalid max chain", + treeId: testTreeId, + prefix: testPrefix, + maxRange: testMaxRange, + maxChain: 0, + anchors: anchors, + signer: signer, + wantErr: true, + }, + { + description: "public key marshal failure", + treeId: testTreeId, + prefix: testPrefix, + maxRange: testMaxRange, + maxChain: testMaxChain, + anchors: []*x509.Certificate{}, + signer: cttestdata.NewSignerWithFixedSig("no pub", testSignature), + wantErr: true, + }, + { + description: "valid log parameters", + treeId: testTreeId, + prefix: testPrefix, + maxRange: testMaxRange, + maxChain: testMaxChain, + anchors: anchors, + signer: signer, + }, + } { + lp, err := NewLogParameters(table.treeId, table.prefix, table.anchors, table.signer, table.maxRange, table.maxChain) + 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 := lp.LogId, logId; !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 := lp.MaxChain, testMaxChain; got != want { + t.Errorf("got max chain %d but wanted %d in test %q", got, want, table.description) + } + if got, want := lp.MaxChain, testMaxChain; got != want { + t.Errorf("got max chain %d but wanted %d in test %q", got, want, table.description) + } + if got, want := len(lp.AnchorList), len(anchors); got != want { + t.Errorf("got %d anchors but wanted %d in test %q", got, want, table.description) + } + if got, want := len(lp.AnchorPool.Subjects()), len(anchors); got != want { + t.Errorf("got %d anchors in pool but wanted %d in test %q", got, want, table.description) + } + } +} + +// TestHandlers checks that we configured all endpoints and that there are no +// unexpected ones. +func TestHandlers(t *testing.T) { + endpoints := map[Endpoint]bool{ + EndpointAddEntry: false, + EndpointGetEntries: false, + EndpointGetSth: false, + EndpointGetProofByHash: false, + EndpointGetConsistencyProof: false, + EndpointGetAnchors: false, + } + i := NewInstance(makeTestLogParameters(t, nil), nil, testDeadline) + for _, handler := range i.Handlers() { + if _, ok := endpoints[handler.endpoint]; !ok { + t.Errorf("got unexpected endpoint: %s", handler.endpoint) + } + endpoints[handler.endpoint] = true + } + for endpoint, ok := range endpoints { + if !ok { + t.Errorf("endpoint %s is not configured", endpoint) + } } } @@ -78,3 +208,25 @@ func TestEndpointPath(t *testing.T) { } } } + +// makeTestLogParameters makes a collection of test log parameters that +// correspond to testLogId, testTreeId, testPrefix, testMaxRange, testMaxChain, +// the anchors in testdata.TrustAnchors, testHashType, and an optional signer. +func makeTestLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { + anchors, err := x509util.NewCertificateList(testdata.TrustAnchors) + if err != nil { + t.Fatalf("must decode trust anchors: %v", err) + } + return &LogParameters{ + LogId: testLogId, + TreeId: testTreeId, + Prefix: testPrefix, + MaxRange: testMaxRange, + MaxChain: testMaxChain, + AnchorPool: x509util.NewCertPool(anchors), + AnchorList: anchors, + KeyUsage: testExtKeyUsage, + Signer: signer, + HashType: testHashType, + } +} diff --git a/server/main.go b/server/main.go index c60f95d..a82faa2 100644 --- a/server/main.go +++ b/server/main.go @@ -69,7 +69,7 @@ func main() { glog.Fatalf("failed setting up log parameters: %v", err) } - i := stfe.NewInstance(lp, client, *rpcDeadline, mux) + i := stfe.NewInstance(lp, client, *rpcDeadline) for _, handler := range i.Handlers() { glog.Infof("adding handler: %s", handler.Path()) mux.Handle(handler.Path(), handler) diff --git a/x509util/testdata/data.go b/x509util/testdata/data.go index 832a3aa..6438ecc 100644 --- a/x509util/testdata/data.go +++ b/x509util/testdata/data.go @@ -164,6 +164,11 @@ MC4CAQAwBQYDK2VwBCIEIKQd3B84w9pB6zJLGljuDyGKfz9uPP6QBeLiFcw0EME4 // NumTrustAnchors is the number of test trust anchors NumTrustAnchors = 2 + // LogPrivateKey is an Ed25519 signing key + LogPrivateKey = []byte(`-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAhqlhKgY/TiEyTIe5BcZKLELGa2kODtJ3S+oMP4JwsA +-----END PRIVATE KEY-----`) + // ExpiredCertificate is a PEM-encoded certificate that is always expired, // i.e., `Not Before`=`Not After`. It is signed by IntermediateCertificate. ExpiredCertificate = []byte(` -- cgit v1.2.3