package stfe import ( "context" "fmt" "time" "net/http" "github.com/golang/glog" "github.com/google/trillian" ) // 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 endpoint string // e.g., add-entry method string // e.g., GET 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(), time.Now().Add(a.instance.Deadline)) defer cancel() if 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.LogParameters.Prefix, a.endpoint, err) a.sendHTTPError(w, statusCode, err) } } // sendHTTPError replies to a request with an error message and a status code. func (a appHandler) sendHTTPError(w http.ResponseWriter, statusCode int, err error) { http.Error(w, http.StatusText(statusCode), statusCode) } func addEntry(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.Info("in addEntry") leaf, appendix, err := NewAddEntryRequest(i.LogParameters, r) if err != nil { return http.StatusBadRequest, err } // request is well-formed, signed, and chains back to a trust anchor trillianRequest := trillian.QueueLeafRequest{ LogId: i.LogParameters.TreeId, Leaf: &trillian.LogLeaf{ LeafValue: leaf, ExtraData: appendix, }, } 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 glog.Infof("Queued leaf: %v", trillianResponse.QueuedLeaf.Leaf.LeafValue) sdi, err := GenV1SDI(i.LogParameters, trillianResponse.QueuedLeaf.Leaf.LeafValue) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed creating signed debug info: %v", err) } response, err := StItemToB64(sdi) if err != nil { return http.StatusInternalServerError, err } if err := WriteJsonResponse(response, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // 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) { glog.Info("handling get-entries request") request, err := NewGetEntriesRequest(i.LogParameters, r) if err != nil { return http.StatusBadRequest, err } // request can be decoded and is mostly valid (range not cmp vs tree size) trillianRequest := trillian.GetLeavesByRangeRequest{ LogId: i.LogParameters.TreeId, StartIndex: request.Start, Count: request.End - request.Start + 1, } trillianResponse, err := i.Client.GetLeavesByRange(ctx, &trillianRequest) if err != nil { return http.StatusInternalServerError, fmt.Errorf("backend GetLeavesByRange request failed: %v", err) } if status, err := checkGetLeavesByRange(trillianResponse, request); err != nil { return status, err } response, err := NewGetEntriesResponse(trillianResponse.Leaves) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed creating GetEntriesResponse: %v", err) } if err := WriteJsonResponse(response, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // getAnchors provides a list of configured trust anchors func getAnchors(_ context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { glog.Info("in getAnchors") data := NewGetAnchorsResponse(i.LogParameters.AnchorList) if err := WriteJsonResponse(data, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // 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) { glog.Info("in getProofByHash") request, err := NewGetProofByHashRequest(r) if err != nil { return http.StatusBadRequest, err } // request can be decoded and is valid trillianRequest := trillian.GetInclusionProofByHashRequest{ LogId: i.LogParameters.TreeId, LeafHash: request.Hash, TreeSize: request.TreeSize, OrderBySequence: true, } trillianResponse, err := i.Client.GetInclusionProofByHash(ctx, &trillianRequest) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed fetching inclusion proof from Trillian backend: %v", err) } // TODO: check the returned tree size in response? // Santity check if len(trillianResponse.Proof) == 0 { return http.StatusNotFound, fmt.Errorf("get-proof-by-hash backend returned no proof") } // TODO: verify that proof is valid? response, err := StItemToB64(NewInclusionProofV1(i.LogParameters.LogId, uint64(request.TreeSize), trillianResponse.Proof[0])) if err != nil { return http.StatusInternalServerError, err } if err := WriteJsonResponse(response, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // getConsistencyProof provides a consistency proof between two STHs func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) { glog.Info("in getConsistencyProof") request, err := NewGetConsistencyProofRequest(r) if err != nil { return http.StatusBadRequest, err } // request can be decoded and is valid trillianRequest := trillian.GetConsistencyProofRequest{ LogId: i.LogParameters.TreeId, FirstTreeSize: int64(request.First), SecondTreeSize: int64(request.Second), } trillianResponse, err := i.Client.GetConsistencyProof(ctx, &trillianRequest) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed fetching consistency proof from Trillian backend: %v", err) } // TODO: santity-checks? response, err := StItemToB64(NewConsistencyProofV1(i.LogParameters.LogId, request.First, request.Second, trillianResponse.Proof)) if err != nil { return http.StatusInternalServerError, err } if err := WriteJsonResponse(response, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil } // getSth provides the most recent STH func getSth(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) { glog.Info("in getSth") trillianRequest := trillian.GetLatestSignedLogRootRequest{ LogId: i.LogParameters.TreeId, } trillianResponse, err := i.Client.GetLatestSignedLogRoot(ctx, &trillianRequest) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed fetching signed tree head from Trillian backend: %v", err) } th, err := NewTreeHeadV1(i.LogParameters, trillianResponse.SignedLogRoot) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed creating tree head: %v", err) } sth, err := GenV1STH(i.LogParameters, th) if err != nil { return http.StatusInternalServerError, fmt.Errorf("failed creating signed tree head: %v", err) } glog.Infof("%v", sth) response, err := StItemToB64(sth) if err != nil { return http.StatusInternalServerError, err } if err := WriteJsonResponse(response, w); err != nil { return http.StatusInternalServerError, err } return http.StatusOK, nil }