Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 75 additions & 58 deletions witness/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,48 +30,67 @@ import (
var MaxRequestBodyBytes int64 = 16 << 10

func NewHTTPHandler(w *Witness) *HTTPHandler {
return &HTTPHandler{witness: w}
return &HTTPHandler{
addCheckpointHandler: NewAddCheckpointHandler(w.Update),
signSubtreeHandler: NewSignSubtreeHandler(w.SignSubtree),
}
}

// HTTPHandler provides tlog-witness compatible handlers intended to be used with the stdlib http server.
type HTTPHandler struct {
witness witness
addCheckpointHandler http.HandlerFunc
signSubtreeHandler http.HandlerFunc
}

// AddCheckpoint is a http.Handler which speaks the tlog-witness protocol for add-checkpoint.
// AddCheckpoint handles HTTP requests conforming to the tlog-witness add-checkpoint protocol.
func (a *HTTPHandler) AddCheckpoint(w http.ResponseWriter, r *http.Request) {
defer func() {
_, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
}()
a.addCheckpointHandler(w, r)
}

oldSize, proof, cp, err := parseBody(http.MaxBytesReader(w, r.Body, MaxRequestBodyBytes))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// SignSubtree is a http.Handler which speaks the tlog-witness protocol for sign-subtree.
func (a *HTTPHandler) SignSubtree(w http.ResponseWriter, r *http.Request) {
a.signSubtreeHandler(w, r)
}

sc, body, contentType, err := a.handleUpdate(r.Context(), oldSize, cp, proof)
if err != nil {
status := http.StatusInternalServerError
w.WriteHeader(status)
return
}
// UpdateFunc knows how to update a log's checkpoint given an old size, a new checkpoint, and a Merkle proof.
type UpdateFunc func(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, uint64, error)

if contentType != "" {
w.Header().Add("Content-Type", contentType)
}
w.WriteHeader(sc)
if len(body) > 0 {
_, _ = w.Write(body)
// NewAddCheckpointHandler returns an http.Handler for the tlog-witness add-checkpoint protocol.
func NewAddCheckpointHandler(update UpdateFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
_, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
}()

oldSize, proof, cp, err := parseBody(http.MaxBytesReader(w, r.Body, MaxRequestBodyBytes))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

sc, body, contentType, err := handleUpdate(r.Context(), update, oldSize, cp, proof)
if err != nil {
status := http.StatusInternalServerError
w.WriteHeader(status)
return
}

if contentType != "" {
w.Header().Add("Content-Type", contentType)
}
w.WriteHeader(sc)
if len(body) > 0 {
_, _ = w.Write(body)
}
}
}

// handleUpdate submits the provided checkpoint to the witness and interprets any errors which may result.
//
// Returns an appropriate HTTP status code, response body, and Content Type representing the outcome.
func (a *HTTPHandler) handleUpdate(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) (int, []byte, string, error) {
sigs, trustedSize, updateErr := a.witness.Update(ctx, oldSize, newCP, proof)
func handleUpdate(ctx context.Context, update UpdateFunc, oldSize uint64, newCP []byte, proof [][]byte) (int, []byte, string, error) {
sigs, trustedSize, updateErr := update(ctx, oldSize, newCP, proof)
// Finally, handle any "soft" error from the update:
if updateErr != nil {
switch {
Expand All @@ -98,38 +117,43 @@ func (a *HTTPHandler) handleUpdate(ctx context.Context, oldSize uint64, newCP []
return http.StatusOK, sigs, "", nil
}

// SignSubtree is a http.Handler which speaks the tlog-witness protocol for sign-subtree.
func (a *HTTPHandler) SignSubtree(w http.ResponseWriter, r *http.Request) {
defer func() {
_, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
}()
// SignSubtreeFunc knows how to verify and sign a subtree.
type SignSubtreeFunc func(ctx context.Context, start, end uint64, subRoot []byte, proof [][]byte, cp []byte) ([]byte, error)

start, end, subRoot, proof, cp, err := parseSubtreeBody(http.MaxBytesReader(w, r.Body, MaxRequestBodyBytes))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// NewSignSubtreeHandler returns an http.Handler for the tlog-witness sign-subtree protocol.
func NewSignSubtreeHandler(signSubtree SignSubtreeFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
_, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
}()

sc, body, contentType, err := a.handleSignSubtree(r.Context(), start, end, subRoot, proof, cp)
if err != nil {
status := http.StatusInternalServerError
w.WriteHeader(status)
return
}
start, end, subRoot, proof, cp, err := parseSubtreeBody(http.MaxBytesReader(w, r.Body, MaxRequestBodyBytes))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

if contentType != "" {
w.Header().Add("Content-Type", contentType)
}
w.WriteHeader(sc)
if len(body) > 0 {
_, _ = w.Write(body)
sc, body, contentType, err := handleSignSubtree(r.Context(), signSubtree, start, end, subRoot, proof, cp)
if err != nil {
status := http.StatusInternalServerError
w.WriteHeader(status)
return
}

if contentType != "" {
w.Header().Add("Content-Type", contentType)
}
w.WriteHeader(sc)
if len(body) > 0 {
_, _ = w.Write(body)
}
}
}

// handleSignSubtree submits the sign-subtree request to the witness and interprets any errors.
func (a *HTTPHandler) handleSignSubtree(ctx context.Context, start, end uint64, subRoot []byte, proof [][]byte, cp []byte) (int, []byte, string, error) {
sigs, err := a.witness.SignSubtree(ctx, start, end, subRoot, proof, cp)
func handleSignSubtree(ctx context.Context, signSubtree SignSubtreeFunc, start, end uint64, subRoot []byte, proof [][]byte, cp []byte) (int, []byte, string, error) {
sigs, err := signSubtree(ctx, start, end, subRoot, proof, cp)
if err != nil {
switch {
case errors.Is(err, ErrUnknownLog):
Expand Down Expand Up @@ -237,10 +261,3 @@ func parseSubtreeBody(r io.Reader) (uint64, uint64, []byte, [][]byte, []byte, er
}
return start, end, subRoot, proof, cp, nil
}

// witness is the contract expected of the backend for HTTPHandler.
// This interface only really exists to make testing easier.
type witness interface {
Update(ctx context.Context, oldSize uint64, newCP []byte, proof [][]byte) ([]byte, uint64, error)
SignSubtree(ctx context.Context, start, end uint64, subRoot []byte, proof [][]byte, cp []byte) ([]byte, error)
}
12 changes: 2 additions & 10 deletions witness/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ func TestParseSubtreeBody(t *testing.T) {
}
}


func TestHandler(t *testing.T) {
for _, test := range []struct {
name string
Expand Down Expand Up @@ -191,10 +190,7 @@ func TestHandler(t *testing.T) {
},
} {
t.Run(test.name, func(t *testing.T) {
a := HTTPHandler{
witness: test.witness,
}
sc, body, ct, err := a.handleUpdate(context.Background(), 0, []byte(testCP), [][]byte{})
sc, body, ct, err := handleUpdate(context.Background(), test.witness.Update, 0, []byte(testCP), [][]byte{})
if err != nil {
t.Fatalf("handleUpdate: %v", err)
}
Expand Down Expand Up @@ -253,10 +249,7 @@ func TestSubtreeHandler(t *testing.T) {
},
} {
t.Run(test.name, func(t *testing.T) {
a := HTTPHandler{
witness: test.witness,
}
sc, body, ct, err := a.handleSignSubtree(context.Background(), 0, 1, []byte{}, [][]byte{}, []byte(testCP))
sc, body, ct, err := handleSignSubtree(context.Background(), test.witness.SignSubtree, 0, 1, []byte{}, [][]byte{}, []byte(testCP))
if err != nil {
t.Fatalf("handleSignSubtree: %v", err)
}
Expand All @@ -273,7 +266,6 @@ func TestSubtreeHandler(t *testing.T) {
}
}


type testWitness struct {
latestCPErr error
latestCP []byte
Expand Down
Loading