From dd19521190f39a8b1704adb724f5f812040f91e4 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Tue, 27 Oct 2020 15:16:24 +0100 Subject: decoupled log instance and info Makes things a bit more modular. As part of this process I also replaced ct/x509 with crypto/x509, which already suits our needs. --- handler.go | 39 +++++++++++++------------- instance.go | 88 ++++++++++++++++++++++++++++++++++++++-------------------- reqres.go | 25 +++++++++++------ server/main.go | 37 ++++++++++++------------ type.go | 10 +++---- x509.go | 43 ++++++++++++++++++++++++++++ 6 files changed, 159 insertions(+), 83 deletions(-) create mode 100644 x509.go diff --git a/handler.go b/handler.go index 33c6979..6e5fe49 100644 --- a/handler.go +++ b/handler.go @@ -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) diff --git a/reqres.go b/reqres.go index 5d4c1bc..a725363 100644 --- a/reqres.go +++ b/reqres.go @@ -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} diff --git a/type.go b/type.go index f691f34..a629259 100644 --- a/type.go +++ b/type.go @@ -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} } diff --git a/x509.go b/x509.go new file mode 100644 index 0000000..cdcd523 --- /dev/null +++ b/x509.go @@ -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 +} -- cgit v1.2.3