diff options
| -rw-r--r-- | handler.go | 39 | ||||
| -rw-r--r-- | instance.go | 88 | ||||
| -rw-r--r-- | reqres.go | 25 | ||||
| -rw-r--r-- | server/main.go | 37 | ||||
| -rw-r--r-- | type.go | 10 | ||||
| -rw-r--r-- | x509.go | 43 | 
6 files changed, 159 insertions, 83 deletions
| @@ -3,6 +3,7 @@ package stfe  import (  	"context"  	"fmt" +	"time"  	"net/http" @@ -13,26 +14,26 @@ import (  // appHandler implements the http.Handler interface, and contains a reference  // to an STFE server instance as well as a function that uses it.  type appHandler struct { -	instance *instance // STFE server instance +	instance *Instance // STFE server instance  	endpoint string    // e.g., add-entry  	method   string    // e.g., GET -	handler  func(context.Context, *instance, http.ResponseWriter, *http.Request) (int, error) +	handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error)  }  // ServeHTTP docdoc  func (a appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { -	ctx, cancel := context.WithDeadline(r.Context(), a.instance.timesource.Now().Add(a.instance.deadline)) +	ctx, cancel := context.WithDeadline(r.Context(), time.Now().Add(a.instance.Deadline))  	defer cancel()  	if r.Method != a.method { -		glog.Warningf("%s: got HTTP %s, wanted HTTP %s", a.instance.prefix+a.endpoint, r.Method, a.method) +		glog.Warningf("%s: got HTTP %s, wanted HTTP %s", a.instance.LogParameters.Prefix+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.prefix, a.endpoint, err) +		glog.Warningf("handler error %s/%s: %v", a.instance.LogParameters.Prefix, a.endpoint, err)  		a.sendHTTPError(w, statusCode, err)  	}  } @@ -42,26 +43,26 @@ func (a appHandler) sendHTTPError(w http.ResponseWriter, statusCode int, err err  	http.Error(w, http.StatusText(statusCode), statusCode)  } -func addEntry(ctx context.Context, i *instance, w http.ResponseWriter, r *http.Request) (int, error) { +func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.Info("in addEntry")  	request, err := NewAddEntryRequest(r)  	if err != nil {  		return http.StatusBadRequest, err  	} // request can be decoded -	leaf, appendix, err := VerifyAddEntryRequest(i.anchors, request) +	leaf, appendix, err := VerifyAddEntryRequest(i.LogParameters, request)  	if err != nil {  		return http.StatusBadRequest, err  	} // valid add-entry request  	trillianRequest := trillian.QueueLeafRequest{ -		LogId: i.logID, +		LogId: i.LogParameters.TreeId,  		Leaf: &trillian.LogLeaf{  			LeafValue: leaf,  			ExtraData: appendix,  		},  	} -	trillianResponse, err := i.client.QueueLeaf(ctx, &trillianRequest) +	trillianResponse, err := i.Client.QueueLeaf(ctx, &trillianRequest)  	if err != nil {  		return http.StatusInternalServerError, fmt.Errorf("backend QueueLeaf request failed: %v", err)  	} // note: more detail could be provided here, see addChainInternal in ctfe @@ -72,7 +73,7 @@ func addEntry(ctx context.Context, i *instance, w http.ResponseWriter, r *http.R  }  // 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) { +func getEntries(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.Info("in getEntries")  	request, err := NewGetEntriesRequest(r)  	if err != nil { @@ -80,11 +81,11 @@ func getEntries(ctx context.Context, i *instance, w http.ResponseWriter, r *http  	} // request can be decoded and is valid  	trillianRequest := trillian.GetLeavesByRangeRequest{ -		LogId:      i.logID, +		LogId:      i.LogParameters.TreeId,  		StartIndex: request.Start,  		Count:      request.End - request.Start + 1,  	} -	trillianResponse, err := i.client.GetLeavesByRange(ctx, &trillianRequest) +	trillianResponse, err := i.Client.GetLeavesByRange(ctx, &trillianRequest)  	if err != nil {  		return http.StatusInternalServerError, fmt.Errorf("backend GetLeavesByRange request failed: %v", err)  	} @@ -113,9 +114,9 @@ func getEntries(ctx context.Context, i *instance, w http.ResponseWriter, r *http  }  // getAnchors provides a list of configured trust anchors -func getAnchors(_ context.Context, i *instance, w http.ResponseWriter, _ *http.Request) (int, error) { +func getAnchors(_ context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) {  	glog.Info("in getAnchors") -	data := NewGetAnchorsResponse(i.anchorsPool.RawCertificates()) +	data := NewGetAnchorsResponse(i.LogParameters.AnchorList)  	if err := WriteJsonResponse(data, w); err != nil {  		return http.StatusInternalServerError, err  	} @@ -123,7 +124,7 @@ func getAnchors(_ context.Context, i *instance, w http.ResponseWriter, _ *http.R  }  // 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) { +func getProofByHash(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.Info("in getProofByHash")  	request, err := NewGetProofByHashRequest(r)  	if err != nil { @@ -131,12 +132,12 @@ func getProofByHash(ctx context.Context, i *instance, w http.ResponseWriter, r *  	} // request can be decoded and is valid  	trillianRequest := trillian.GetInclusionProofByHashRequest{ -		LogId:           i.logID, +		LogId:           i.LogParameters.TreeId,  		LeafHash:        request.Hash,  		TreeSize:        request.TreeSize,  		OrderBySequence: true,  	} -	trillianResponse, err := i.client.GetInclusionProofByHash(ctx, &trillianRequest) +	trillianResponse, err := i.Client.GetInclusionProofByHash(ctx, &trillianRequest)  	if err != nil {  		return http.StatusInternalServerError, fmt.Errorf("failed fetching inclusion proof from Trillian backend: %v", err)  	} @@ -159,13 +160,13 @@ func getProofByHash(ctx context.Context, i *instance, w http.ResponseWriter, r *  }  // getConsistencyProof provides a consistency proof between two STHs -func getConsistencyProof(ctx context.Context, i *instance, w http.ResponseWriter, r *http.Request) (int, error) { +func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.Info("in getConsistencyProof")  	return http.StatusOK, nil // TODO  }  // getSth provides the most recent STH -func getSth(ctx context.Context, i *instance, w http.ResponseWriter, r *http.Request) (int, error) { +func getSth(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.Info("in getSth")  	return http.StatusOK, nil // TODO  } diff --git a/instance.go b/instance.go index c8aaca3..d4fc004 100644 --- a/instance.go +++ b/instance.go @@ -1,53 +1,81 @@  package stfe  import ( +	"crypto" +	"crypto/x509" +	"fmt"  	"time" +	"encoding/base64"  	"net/http"  	"github.com/golang/glog"  	"github.com/google/trillian" - -	"github.com/google/certificate-transparency-go/trillian/ctfe" -	ctutil "github.com/google/certificate-transparency-go/trillian/util"  ) -// instance groups information about a specific STFE instance. -type instance struct { -	prefix      string -	logID       int64 -	client      trillian.TrillianLogClient -	deadline    time.Duration -	anchors     ctfe.CertValidationOpts -	anchorsPool ctfe.PEMCertPool // TODO: merge anchors and anchorsPool -	timesource  ctutil.TimeSource +// Instance is an instance of a particular log front-end +type Instance struct { +	LogParameters *LogParameters +	Client        trillian.TrillianLogClient +	Deadline      time.Duration +} + +// LogParameters is a collection of log parameters +type LogParameters struct { +	LogId      []byte // used externally by everyone +	TreeId     int64  // used internally by Trillian +	Prefix     string +	AnchorPool *x509.CertPool      // for chain verification +	AnchorList []*x509.Certificate // for access to the raw certificates +	Signer     crypto.Signer +} + +// NewInstance returns an initialized Instance +func NewInstance(lp *LogParameters, client trillian.TrillianLogClient, deadline time.Duration, mux *http.ServeMux) (*Instance, error) { +	i := &Instance{ +		LogParameters: lp, +		Client:        client, +		Deadline:      deadline, +	} +	i.registerHandlers(mux) +	return i, nil  } -// NewInstance returns a new STFE instance -func NewInstance(prefix string, id int64, client trillian.TrillianLogClient, deadline time.Duration, timesource ctutil.TimeSource, anchors ctfe.CertValidationOpts, anchorsPool ctfe.PEMCertPool) *instance { -	return &instance{ -		prefix:      prefix, -		logID:       id, -		client:      client, -		deadline:    deadline, -		timesource:  timesource, -		anchors:     anchors, -		anchorsPool: anchorsPool, +// NewLogParameters returns an initialized LogParameters +func NewLogParameters(logId []byte, treeId int64, prefix string, anchorPath string) (*LogParameters, error) { +	anchorList, anchorPool, err := LoadTrustAnchors(anchorPath) +	if err != nil { +		return nil, err  	} + +	return &LogParameters{ +		LogId:      logId, +		TreeId:     treeId, +		Prefix:     prefix, +		AnchorPool: anchorPool, +		AnchorList: anchorList, +	}, nil +} + +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))  } -// addEndpoints registers STFE handler functions for the respective HTTP paths -func (i *instance) AddEndpoints(mux *http.ServeMux) { +func (i *Instance) registerHandlers(mux *http.ServeMux) {  	for _, endpoint := range []struct {  		path    string  		handler appHandler  	}{ -		{i.prefix + "/add-entry", appHandler{instance: i, handler: addEntry, endpoint: "add-entry", method: http.MethodPost}}, -		{i.prefix + "/get-entries", appHandler{instance: i, handler: getEntries, endpoint: "get-entries", method: http.MethodGet}}, -		{i.prefix + "/get-anchors", appHandler{instance: i, handler: getAnchors, endpoint: "get-anchors", method: http.MethodGet}}, -		{i.prefix + "/get-proof-by-hash", appHandler{instance: i, handler: getProofByHash, endpoint: "get-proof-by-hash", method: http.MethodGet}}, -		{i.prefix + "/get-consistency-proof", appHandler{instance: i, handler: getConsistencyProof, endpoint: "get-consistency-proof", method: http.MethodGet}}, -		{i.prefix + "/get-sth", appHandler{instance: i, handler: getSth, endpoint: "get-sth", method: http.MethodGet}}, +		{i.LogParameters.Prefix + "/add-entry", appHandler{instance: i, handler: addEntry, endpoint: "add-entry", method: http.MethodPost}}, +		{i.LogParameters.Prefix + "/get-entries", appHandler{instance: i, handler: getEntries, endpoint: "get-entries", method: http.MethodGet}}, +		{i.LogParameters.Prefix + "/get-anchors", appHandler{instance: i, handler: getAnchors, endpoint: "get-anchors", method: http.MethodGet}}, +		{i.LogParameters.Prefix + "/get-proof-by-hash", appHandler{instance: i, handler: getProofByHash, endpoint: "get-proof-by-hash", method: http.MethodGet}}, +		{i.LogParameters.Prefix + "/get-consistency-proof", appHandler{instance: i, handler: getConsistencyProof, endpoint: "get-consistency-proof", method: http.MethodGet}}, +		{i.LogParameters.Prefix + "/get-sth", appHandler{instance: i, handler: getSth, endpoint: "get-sth", method: http.MethodGet}},  	} {  		glog.Infof("adding handler for %v", endpoint.path)  		mux.Handle(endpoint.path, endpoint.handler) @@ -6,14 +6,13 @@ import (  	"crypto/ecdsa"  	"crypto/rsa" +	"crypto/x509"  	"encoding/base64"  	"encoding/json"  	"io/ioutil"  	"net/http"  	"github.com/google/certificate-transparency-go/tls" -	"github.com/google/certificate-transparency-go/trillian/ctfe" -	"github.com/google/certificate-transparency-go/x509"  	"github.com/google/trillian"  ) @@ -137,9 +136,9 @@ func NewGetEntryResponse(leaf, appendix []byte) (GetEntryResponse, error) {  	}  	return GetEntryResponse{ -		Leaf: base64.StdEncoding.EncodeToString(leaf), +		Leaf:      base64.StdEncoding.EncodeToString(leaf),  		Signature: base64.StdEncoding.EncodeToString(app.Signature), -		Chain: chain, +		Chain:     chain,  	}, nil  } @@ -178,7 +177,7 @@ func NewGetAnchorsResponse(anchors []*x509.Certificate) GetAnchorsResponse {  // VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should  // be inserted into the log.  The corresponding leaf and appendix is returned. -func VerifyAddEntryRequest(anchors ctfe.CertValidationOpts, r AddEntryRequest) ([]byte, []byte, error) { +func VerifyAddEntryRequest(ld *LogParameters, r AddEntryRequest) ([]byte, []byte, error) {  	item, err := StItemFromB64(r.Item)  	if err != nil {  		return nil, nil, fmt.Errorf("failed decoding StItem: %v", err) @@ -193,13 +192,21 @@ func VerifyAddEntryRequest(anchors ctfe.CertValidationOpts, r AddEntryRequest) (  	if err != nil {  		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err)  	} -	chain := make([][]byte, 0, 1) -	chain = append(chain, certificate) -	x509chain, err := ctfe.ValidateChain(chain, anchors) +	c, err := x509.ParseCertificate(certificate) +	if err != nil { +		return nil, nil, fmt.Errorf("failed decoding certificate: %v", err) +	} +	opts := x509.VerifyOptions{ +		Roots: ld.AnchorPool, +	} +	chains, err := c.Verify(opts)  	if err != nil {  		return nil, nil, fmt.Errorf("chain verification failed: %v", err)  	} -	c := x509chain[0] +	if len(chains) == 0 { +		return nil, nil, fmt.Errorf("chain verification failed: no chain") +	} +	x509chain := chains[0]  	signature, err := base64.StdEncoding.DecodeString(r.Signature)  	if err != nil { diff --git a/server/main.go b/server/main.go index 618d40b..84d92ea 100644 --- a/server/main.go +++ b/server/main.go @@ -1,4 +1,4 @@ -// Package main provides an STFE binary +// Package main provides an STFE server binary  package main  import ( @@ -11,19 +11,15 @@ import (  	"github.com/google/trillian"  	"github.com/system-transparency/stfe"  	"google.golang.org/grpc" - -	"github.com/google/certificate-transparency-go/trillian/ctfe" -	ctutil "github.com/google/certificate-transparency-go/trillian/util" -	"github.com/google/certificate-transparency-go/x509"  )  var ( -	httpEndpoint   = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") -	rpcBackend     = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") -	prefix         = flag.String("prefix", "/st/v1", "a prefix that proceeds each endpoint path") -	trillianID     = flag.Int64("trillian_id", 5991359069696313945, "log identifier in the Trillian database") -	rpcDeadline    = flag.Duration("rpc_deadline", time.Second*10, "deadline for backend RPC requests") -	anchorsPemFile = flag.String("anchors_file", "testdata/chain/rgdd-root.pem", "path to a file containing PEM-encoded X.509 root certificates") +	httpEndpoint = flag.String("http_endpoint", "localhost:6965", "host:port specification of where stfe serves clients") +	rpcBackend   = flag.String("log_rpc_server", "localhost:6962", "host:port specification of where Trillian serves clients") +	prefix       = flag.String("prefix", "/st/v1", "a prefix that proceeds each endpoint path") +	trillianID   = flag.Int64("trillian_id", 5991359069696313945, "log identifier in the Trillian database") +	rpcDeadline  = flag.Duration("rpc_deadline", time.Second*10, "deadline for backend RPC requests") +	anchorPath   = flag.String("anchor_path", "testdata/chain/rgdd-root.pem", "path to a file containing PEM-encoded X.509 root certificates")  )  func main() { @@ -35,21 +31,22 @@ func main() {  	if err != nil {  		glog.Fatal(err)  	} +	client := trillian.NewTrillianLogClient(conn)  	glog.Info("Creating HTTP request multiplexer")  	mux := http.NewServeMux()  	http.Handle("/", mux) -	// TODO: proper setup -	glog.Info("Loading trust anchors") -	cert_pool := ctfe.NewPEMCertPool() -	cert_pool.AppendCertsFromPEMFile(*anchorsPemFile) -	anchors := ctfe.NewCertValidationOpts(cert_pool, time.Now(), true, false, nil, nil, false, []x509.ExtKeyUsage{}) -	glog.Infof("%v", cert_pool.Subjects()) +	lp, err := stfe.NewLogParameters([]byte("rgdd"), *trillianID, *prefix, *anchorPath) +	if err != nil { +		glog.Fatalf("failed setting up log parameters: %v", err) +	} -	glog.Info("Creating STFE server instance") -	stfe_server := stfe.NewInstance(*prefix, *trillianID, trillian.NewTrillianLogClient(conn), *rpcDeadline, new(ctutil.SystemTimeSource), anchors, *cert_pool) -	stfe_server.AddEndpoints(mux) +	i, err := stfe.NewInstance(lp, client, *rpcDeadline, mux) +	if err != nil { +		glog.Fatalf("failed setting up log instance: %v", err) +	} +	glog.Infof("Configured: %s", i)  	glog.Infof("Serving on %v%v", *httpEndpoint, *prefix)  	srv := http.Server{Addr: *httpEndpoint} @@ -3,10 +3,10 @@ package stfe  import (  	"fmt" +	"crypto/x509"  	"encoding/base64"  	"github.com/google/certificate-transparency-go/tls" -	"github.com/google/certificate-transparency-go/x509"  	"github.com/google/trillian"  ) @@ -140,8 +140,8 @@ func StItemFromB64(s string) (StItem, error) {  // Appendix is extra data that Trillian can store about a leaf  type Appendix struct { -	Signature []byte `tls:"minlen:0,maxlen:16383"` -	Chain []RawCertificate `tls:"minlen:0,maxlen:65535"` +	Signature []byte           `tls:"minlen:0,maxlen:16383"` +	Chain     []RawCertificate `tls:"minlen:0,maxlen:65535"`  }  // RawCertificate is a serialized X.509 certificate @@ -153,7 +153,7 @@ type RawCertificate struct {  func NewAppendix(x509Chain []*x509.Certificate, signature []byte) Appendix {  	chain := make([]RawCertificate, 0, 2) // TODO: base length on config param  	for _, c := range x509Chain { -		chain = append(chain, RawCertificate{ c.Raw }) +		chain = append(chain, RawCertificate{c.Raw})  	} -	return Appendix{ Signature: signature, Chain: chain } +	return Appendix{Signature: signature, Chain: chain}  } @@ -0,0 +1,43 @@ +package stfe + +import ( +	"fmt" + +	"crypto/x509" +	"encoding/pem" +	"io/ioutil" +) + +// LoadTrustAnchors loads a list of PEM-encoded certificates from file +func LoadTrustAnchors(path string) ([]*x509.Certificate, *x509.CertPool, error) { +	rest, err := ioutil.ReadFile(path) +	if err != nil { +		return nil, nil, fmt.Errorf("failed reading trust anchors: %v", err) +	} + +	pool := x509.NewCertPool() +	var anchors []*x509.Certificate +	for len(rest) > 0 { +		var block *pem.Block +		block, rest = pem.Decode(rest) +		if block == nil { +			break +		} +		if block.Type != "CERTIFICATE" { +			return nil, nil, fmt.Errorf("unexpected PEM block type: %s", block.Type) +		} + +		certificate, err := x509.ParseCertificate(block.Bytes) +		if err != nil { +			return nil, nil, fmt.Errorf("invalid trust anchor before rest(%s): %v", rest, err) +		} + +		anchors = append(anchors, certificate) +		pool.AddCert(certificate) +	} + +	if len(anchors) == 0 { +		return nil, nil, fmt.Errorf("found no valid trust anchor in: %s", path) +	} +	return anchors, pool, nil +} | 
