From bf521e82e0128a2cc31a51f866fd5a86dc677a87 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 23 Oct 2020 11:46:58 +0200 Subject: refactored type.go Moved structures for in/out HTTP data into reqres.go and added basic doc comments. A few minor edits as well to make things consistent. --- handler.go | 5 +- reqres.go | 119 ++++++++++++++++++++++++++++++++++ type.go | 204 +++++++++++++++++------------------------------------------ type_test.go | 19 ++---- 4 files changed, 184 insertions(+), 163 deletions(-) create mode 100644 reqres.go diff --git a/handler.go b/handler.go index 10defba..b6a52f8 100644 --- a/handler.go +++ b/handler.go @@ -115,9 +115,8 @@ func unpackRequest(r *http.Request, unpack interface{}) error { // 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("in getEntries") - - var request GetEntriesRequest - if err := request.Unpack(r); err != nil { + request, err := NewGetEntriesRequest(r) + if err != nil { return http.StatusBadRequest, err } diff --git a/reqres.go b/reqres.go new file mode 100644 index 0000000..a228c76 --- /dev/null +++ b/reqres.go @@ -0,0 +1,119 @@ +package stfe + +import ( + "fmt" + "strconv" + + "encoding/base64" + "net/http" + + "github.com/google/certificate-transparency-go/tls" + "github.com/google/trillian" +) + +// AddEntryRequest is a collection of add-entry input parameters +type AddEntryRequest struct { + Item string `json:"item"` + Signature string `json:"signature"` + Certificate string `json:"certificate"` +} + +// GetEntriesRequest is a collection of get-entry input parameters +type GetEntriesRequest struct { + Start int64 `json:"start"` + End int64 `json:"end"` +} + +// GetProofByHashRequest is a collection of get-proof-by-hash input parameters +type GetProofByHashRequest struct { + Hash []byte `json:"hash"` + TreeSize int64 `json:"tree_size"` +} + +// GetEntryResponse is an assembled log entry and its associated appendix +type GetEntryResponse struct { + Leaf string `json:"leaf"` // base64-encoded StItem + Signature string `json:"signature"` // base64-encoded DigitallySigned + Chain []string `json:chain` // base64-encoded X.509 certificates +} + +// GetEntriesResponse is an assembled get-entries responses +type GetEntriesResponse struct { + Entries []GetEntryResponse `json:"entries"` +} + +// GetProofByHashResponse is an assembled inclusion proof response +type GetProofByHashResponse struct { + InclusionProof string `json:"inclusion_proof"` // base64-encoded StItem +} + +// NewGetEntriesRequest parses and sanitizes the URL-encoded get-entries +// parameters from an incoming HTTP request. +func NewGetEntriesRequest(httpRequest *http.Request) (GetEntriesRequest, error) { + start, err := strconv.ParseInt(httpRequest.FormValue("start"), 10, 64) + if err != nil { + return GetEntriesRequest{}, fmt.Errorf("bad start parameter: %v", err) + } + end, err := strconv.ParseInt(httpRequest.FormValue("end"), 10, 64) + if err != nil { + return GetEntriesRequest{}, fmt.Errorf("bad end parameter: %v", err) + } + + if start < 0 { + return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must have a non-negative value", start) + } + if start > end { + return GetEntriesRequest{}, fmt.Errorf("bad parameters: start(%v) must be larger than end(%v)", start, end) + } + // TODO: check that range is not larger than the max range. Yes -> truncate + // TODO: check that end is not past the most recent STH. Yes -> truncate + return GetEntriesRequest{Start: start, End: end}, nil +} + +// NewGetProofByHashRequest parses and sanitizes the URL-encoded +// get-proof-by-hash parameters from an incoming HTTP request. +func NewGetProofByHashRequest(httpRequest *http.Request) (GetProofByHashRequest, error) { + treeSize, err := strconv.ParseInt(httpRequest.FormValue("tree_size"), 10, 64) + if err != nil { + return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: %v", err) + } + if treeSize < 0 { + return GetProofByHashRequest{}, fmt.Errorf("bad tree_size parameter: negative value") + } + // TODO: check that tree size is not past STH.tree_size + + hash, err := base64.StdEncoding.DecodeString(httpRequest.FormValue("hash")) + if err != nil { + return GetProofByHashRequest{}, fmt.Errorf("bad hash parameter: %v", err) + } + return GetProofByHashRequest{TreeSize: treeSize, Hash: hash}, nil +} + +// NewGetEntryResponse assembles a log entry and its appendix +func NewGetEntryResponse(leaf []byte) GetEntryResponse { + return GetEntryResponse{ + Leaf: base64.StdEncoding.EncodeToString(leaf), + // TODO: add signature and chain + } +} + +// NewGetEntriesResponse assembles a get-entries response +func NewGetEntriesResponse(leaves []*trillian.LogLeaf) (GetEntriesResponse, error) { + entries := make([]GetEntryResponse, 0, len(leaves)) + for _, leaf := range leaves { + entries = append(entries, NewGetEntryResponse(leaf.GetLeafValue())) // TODO: add signature and chain + } + return GetEntriesResponse{entries}, nil +} + +// NewGetProofByHashResponse assembles a get-proof-by-hash response +func NewGetProofByHashResponse(treeSize uint64, inclusionProof *trillian.Proof) (*GetProofByHashResponse, error) { + item := NewInclusionProofV1([]byte("TODO: add log ID"), treeSize, inclusionProof) + b, err := tls.Marshal(item) + if err != nil { + return nil, fmt.Errorf("tls marshal failed: %v", err) + } + return &GetProofByHashResponse{ + InclusionProof: base64.StdEncoding.EncodeToString(b), + }, nil +} diff --git a/type.go b/type.go index a734411..d47996e 100644 --- a/type.go +++ b/type.go @@ -2,10 +2,8 @@ package stfe import ( "fmt" - "strconv" "encoding/base64" - "net/http" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian" @@ -23,25 +21,6 @@ const ( StFormatChecksumV1 = 5 ) -func (f StFormat) String() string { - switch f { - case StFormatReserved: - return "reserved" - case StFormatSignedTreeHeadV1: - return "signed_tree_head_v1" - case StFormatSignedDebugInfoV1: - return "signed_debug_info_v1" - case StFormatConsistencyProofV1: - return "consistency_proof_v1" - case StFormatInclusionProofV1: - return "inclusion_proof_v1" - case StFormatChecksumV1: - return "checksum_v1" - default: - return fmt.Sprintf("Unknown StFormat: %d", f) - } -} - // StItem references a versioned item based on a given format specifier. type StItem struct { Format StFormat `tls:"maxval:65535"` @@ -50,63 +29,37 @@ type StItem struct { // TODO: add more items } -func (i StItem) String() string { - switch i.Format { - case StFormatChecksumV1: - return fmt.Sprintf("%s %s", i.Format, *i.ChecksumV1) - default: - return fmt.Sprintf("unknown StItem: %s", i.Format) - } -} - -func StItemFromB64(s string) (*StItem, error) { - b, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return nil, fmt.Errorf("base64 decoding failed: %v", err) - } - - var item StItem - extra, err := tls.Unmarshal(b, &item) - if err != nil { - return nil, fmt.Errorf("tls unmarshal failed: %v", err) - } else if len(extra) > 0 { - return nil, fmt.Errorf("tls unmarshal found extra data: %v", extra) - } - return &item, nil -} - // ChecksumV1 associates a package name with an arbitrary checksum value type ChecksumV1 struct { Package []byte `tls:"minlen:0,maxlen:255"` Checksum []byte `tls:"minlen:32,maxlen:255"` } -// NewChecksumV1 creates a new StItem of type checksum_v1 -func NewChecksumV1(name string, checksum []byte) (StItem, error) { - return StItem{ - Format: StFormatChecksumV1, - ChecksumV1: &ChecksumV1{ - Package: []byte(name), - Checksum: checksum, - }, - }, nil // TODO: error handling -} - -func (i ChecksumV1) String() string { - return fmt.Sprintf("%v %v", string(i.Package), base64.StdEncoding.EncodeToString(i.Checksum)) +// InclusionProofV1 is a Merkle tree inclusion proof, see RFC 6962/bis (§4.12) +type InclusionProofV1 struct { + LogID []byte `tls:"minlen:2,maxlen:127"` + TreeSize uint64 + LeafIndex uint64 + InclusionPath []NodeHash `tls:"minlen:1,maxlen:65535"` } +// NodeHash is a hashed Merkle tree node, see RFC 6962/bis (§4.9) type NodeHash struct { Data []byte `tls:"minlen:32,maxlen:255"` } -type InclusionProofV1 struct { - LogID []byte `tls:"minlen:2,maxlen:127"` - TreeSize uint64 - LeafIndex uint64 - InclusionPath []NodeHash `tls:"minlen:1,maxlen:65535"` +// NewChecksumV1 creates a new StItem of type checksum_v1 +func NewChecksumV1(identifier []byte, checksum []byte) StItem { + return StItem{ + Format: StFormatChecksumV1, + ChecksumV1: &ChecksumV1{ + Package: identifier, + Checksum: checksum, + }, + } } +// NewInclusionProofV1 creates a new StItem of type inclusion_proof_v1 func NewInclusionProofV1(logID []byte, treeSize uint64, proof *trillian.Proof) StItem { inclusionPath := make([]NodeHash, 0, len(proof.Hashes)) for _, hash := range proof.Hashes { @@ -124,103 +77,62 @@ func NewInclusionProofV1(logID []byte, treeSize uint64, proof *trillian.Proof) S } } -// AddEntryRequest is a collection of add-entry input parameters -type AddEntryRequest struct { - Item string `json:"item"` - Signature string `json:"signature"` - Certificate string `json:"certificate"` -} - -// GetEntriesRequest is a collection of get-entry input parameters -type GetEntriesRequest struct { - Start int64 - End int64 -} - -func (r *GetEntriesRequest) Unpack(httpRequest *http.Request) error { - var err error - - r.Start, err = strconv.ParseInt(httpRequest.FormValue("start"), 10, 64) - if err != nil { - return fmt.Errorf("bad start parameter: %v", err) - } - r.End, err = strconv.ParseInt(httpRequest.FormValue("end"), 10, 64) - if err != nil { - return fmt.Errorf("bad end parameter: %v", err) - } - - if r.Start < 0 { - return fmt.Errorf("bad parameters: start(%v) must have a non-negative value", r.Start) - } - if r.Start > r.End { - return fmt.Errorf("bad parameters: start(%v) must be larger than end(%v)", r.Start, r.End) +func (f StFormat) String() string { + switch f { + case StFormatReserved: + return "reserved" + case StFormatSignedTreeHeadV1: + return "signed_tree_head_v1" + case StFormatSignedDebugInfoV1: + return "signed_debug_info_v1" + case StFormatConsistencyProofV1: + return "consistency_proof_v1" + case StFormatInclusionProofV1: + return "inclusion_proof_v1" + case StFormatChecksumV1: + return "checksum_v1" + default: + return fmt.Sprintf("Unknown StFormat: %d", f) } - // TODO: check that range is not larger than the max range. Yes -> truncate - // TODO: check that end is not past the most recent STH. Yes -> truncate - return nil -} - -type GetEntryResponse struct { - Leaf string `json:"leaf"` - Signature string `json:"signature"` - Chain []string `json:chain` } -func NewGetEntryResponse(leaf []byte) GetEntryResponse { - return GetEntryResponse{ - Leaf: base64.StdEncoding.EncodeToString(leaf), - // TODO: add signature and chain +func (i StItem) String() string { + switch i.Format { + case StFormatChecksumV1: + return fmt.Sprintf("Format(%s): %s", i.Format, *i.ChecksumV1) + case StFormatInclusionProofV1: + return fmt.Sprintf("Format(%s): %s", i.Format, *i.InclusionProofV1) + default: + return fmt.Sprintf("unknown StItem: %s", i.Format) } } -type GetEntriesResponse struct { - Entries []GetEntryResponse `json:"entries"` +func (i ChecksumV1) String() string { + return fmt.Sprintf("Package(%v) Checksum(%v)", string(i.Package), base64.StdEncoding.EncodeToString(i.Checksum)) } -func NewGetEntriesResponse(leaves []*trillian.LogLeaf) (GetEntriesResponse, error) { - entries := make([]GetEntryResponse, 0, len(leaves)) - for _, leaf := range leaves { - entries = append(entries, NewGetEntryResponse(leaf.GetLeafValue())) // TODO: add signature and chain +func (i InclusionProofV1) String() string { + path := make([]string, 0, len(i.InclusionPath)) + for _, hash := range i.InclusionPath { + path = append(path, base64.StdEncoding.EncodeToString(hash.Data)) } - return GetEntriesResponse{entries}, nil -} -type GetProofByHashRequest struct { - Hash []byte - TreeSize int64 + return fmt.Sprintf("LogID(%s) TreeSize(%d) LeafIndex(%d) AuditPath(%v)", base64.StdEncoding.EncodeToString(i.LogID), i.TreeSize, i.LeafIndex, path) } -func NewGetProofByHashRequest(httpRequest *http.Request) (*GetProofByHashRequest, error) { - var r GetProofByHashRequest - var err error - - r.TreeSize, err = strconv.ParseInt(httpRequest.FormValue("tree_size"), 10, 64) - if err != nil { - return nil, fmt.Errorf("bad tree_size parameter: %v", err) - } - if r.TreeSize < 0 { - return nil, fmt.Errorf("bad tree_size parameter: negative value") - } - // TODO: check that tree size is not past STH.tree_size - - r.Hash, err = base64.StdEncoding.DecodeString(httpRequest.FormValue("hash")) +// StItemFromB64 creates an StItem from a serialized and base64-encoded string +func StItemFromB64(s string) (*StItem, error) { + b, err := base64.StdEncoding.DecodeString(s) if err != nil { - return nil, fmt.Errorf("bad hash parameter: %v", err) + return nil, fmt.Errorf("base64 decoding failed: %v", err) } - return &r, nil -} -type GetProofByHashResponse struct { - InclusionProof string `json:"inclusion_proof"` -} - -func NewGetProofByHashResponse(treeSize uint64, inclusionProof *trillian.Proof) (*GetProofByHashResponse, error) { - item := NewInclusionProofV1([]byte("TODO: add log ID"), treeSize, inclusionProof) - b, err := tls.Marshal(item) + var item StItem + extra, err := tls.Unmarshal(b, &item) if err != nil { - return nil, fmt.Errorf("tls marshal failed: %v", err) + return nil, fmt.Errorf("tls unmarshal failed: %v", err) + } else if len(extra) > 0 { + return nil, fmt.Errorf("tls unmarshal found extra data: %v", extra) } - return &GetProofByHashResponse{ - InclusionProof: base64.StdEncoding.EncodeToString(b), - }, nil + return &item, nil } diff --git a/type_test.go b/type_test.go index bcd66e6..f4e7b40 100644 --- a/type_test.go +++ b/type_test.go @@ -9,27 +9,18 @@ import ( ) func ExampleNewChecksumV1() { - name := "foobar-1.2.3" + name := []byte("foobar-1.2.3") hasher := sha256.New() hasher.Write([]byte(name)) checksum := hasher.Sum(nil) // hash of package name - item, err := NewChecksumV1(name, checksum) - if err != nil { - fmt.Printf("failed creating checksum item: %v", err) - return - } + item := NewChecksumV1(name, checksum) fmt.Printf("%s\n", item) - // Output: checksum_v1 foobar-1.2.3 UOeWe84malBvj2FLtQlr66WA0gUEa5GPR9I7LsYm114= + // Output: Format(checksum_v1): Package(foobar-1.2.3) Checksum(UOeWe84malBvj2FLtQlr66WA0gUEa5GPR9I7LsYm114=) } func ExampleMarshalChecksumV1() { - item, err := NewChecksumV1("foobar-1.2.3", make([]byte, 32)) - if err != nil { - fmt.Printf("failed creating checksum item: %v", err) - return - } - + item := NewChecksumV1([]byte("foobar-1.2.3"), make([]byte, 32)) b, err := tls.Marshal(item) if err != nil { fmt.Printf("tls.Marshal() failed: %v", err) @@ -52,5 +43,5 @@ func ExampleUnmarshalChecksumV1() { return } fmt.Printf("%v\n", item) - // Output: checksum_v1 foobar-1.2.3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + // Output: Format(checksum_v1): Package(foobar-1.2.3) Checksum(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) } -- cgit v1.2.3