diff options
| -rw-r--r-- | handler_test.go | 6 | ||||
| -rw-r--r-- | instance.go | 45 | ||||
| -rw-r--r-- | instance_test.go | 178 | ||||
| -rw-r--r-- | server/main.go | 2 | ||||
| -rw-r--r-- | 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(` | 
