aboutsummaryrefslogtreecommitdiff
path: root/types/http.go
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordberg.se>2021-05-25 11:29:29 +0200
committerLinus Nordberg <linus@nordberg.se>2021-05-25 11:29:29 +0200
commitf9aae584c787950e84cf3b098290a0c73330d8ac (patch)
treed3a018493954529794de3c21c9f4f6b0c846a925 /types/http.go
parent533f683ef1ae999c2fdc0086cbc3de4e675d1e33 (diff)
parent6a20aec8e8a93ce11f8b940659f49c889f94aef1 (diff)
Merge branch 'design' of github.com:system-transparency/stfe into design
Diffstat (limited to 'types/http.go')
-rw-r--r--types/http.go188
1 files changed, 188 insertions, 0 deletions
diff --git a/types/http.go b/types/http.go
new file mode 100644
index 0000000..8bbe26d
--- /dev/null
+++ b/types/http.go
@@ -0,0 +1,188 @@
+package types
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+)
+
+const (
+ // HeaderPrefix is the start of every ST log HTTP header key
+ HeaderPrefix = "stlog-"
+
+ // New leaf
+ HeaderShardHint = HeaderPrefix + "shard_hint"
+ HeaderChecksum = HeaderPrefix + "checksum"
+ HeaderSignatureOverMessage = HeaderPrefix + "signature_over_message"
+ HeaderVerificationKey = HeaderPrefix + "verification_key"
+ HeaderDomainHint = HeaderPrefix + "domain_hint"
+
+ // Inclusion proof
+ HeaderLeafHash = HeaderPrefix + "leaf_hash"
+ HeaderLeafIndex = HeaderPrefix + "leaf_index"
+ HeaderInclusionPath = HeaderPrefix + "inclusion_path"
+
+ // Consistency proof
+ HeaderNewSize = HeaderPrefix + "new_size"
+ HeaderOldSize = HeaderPrefix + "old_size"
+ HeaderConsistencyPath = HeaderPrefix + "consistency_path"
+
+ // Range of leaves
+ HeaderStartSize = HeaderPrefix + "start_size"
+ HeaderEndSize = HeaderPrefix + "end_size"
+
+ // Tree head
+ HeaderTimestamp = HeaderPrefix + "timestamp"
+ HeaderTreeSize = HeaderPrefix + "tree_size"
+ HeaderRootHash = HeaderPrefix + "root_hash"
+
+ // Signature and signer identity
+ HeaderSignature = HeaderPrefix + "signature"
+ HeaderKeyHash = HeaderPrefix + "key_hash"
+)
+
+// ToHTTP returns a signed tree-head as HTTP key-value pairs
+func (sth *SignedTreeHead) ToHTTP() ([]byte, error) {
+ hdr := http.Header{}
+ hdr.Add(HeaderTimestamp, strconv.FormatUint(sth.Timestamp, 10))
+ hdr.Add(HeaderTreeSize, strconv.FormatUint(sth.TreeSize, 10))
+ hdr.Add(HeaderRootHash, hex.EncodeToString(sth.RootHash[:]))
+ for _, sigident := range sth.SigIdent {
+ hdr.Add(HeaderSignature, hex.EncodeToString(sigident.Signature[:]))
+ hdr.Add(HeaderKeyHash, hex.EncodeToString(sigident.KeyHash[:]))
+ }
+ return headerToBytes(hdr)
+}
+
+// ToHTTP returns a consistency proof as HTTP key-value pairs
+func (p *ConsistencyProof) ToHTTP() ([]byte, error) {
+ hdr := http.Header{}
+ hdr.Add(HeaderNewSize, strconv.FormatUint(p.NewSize, 10))
+ hdr.Add(HeaderOldSize, strconv.FormatUint(p.OldSize, 10))
+ for _, hash := range p.Path {
+ hdr.Add(HeaderConsistencyPath, hex.EncodeToString(hash[:]))
+ }
+ return headerToBytes(hdr)
+}
+
+// ToHTTP returns an inclusion proof as HTTP key-value pairs
+func (p *InclusionProof) ToHTTP() ([]byte, error) {
+ hdr := http.Header{}
+ hdr.Add(HeaderTreeSize, strconv.FormatUint(p.TreeSize, 10))
+ hdr.Add(HeaderLeafIndex, strconv.FormatUint(p.LeafIndex, 10))
+ for _, hash := range p.Path {
+ hdr.Add(HeaderInclusionPath, hex.EncodeToString(hash[:]))
+ }
+ return headerToBytes(hdr)
+}
+
+// ToHTTP returns a leaf as HTTP key-value pairs
+func (l *Leaf) ToHTTP() ([]byte, error) {
+ hdr := http.Header{}
+ hdr.Add(HeaderShardHint, strconv.FormatUint(l.ShardHint, 10))
+ hdr.Add(HeaderChecksum, hex.EncodeToString(l.Checksum[:]))
+ hdr.Add(HeaderSignature, hex.EncodeToString(l.Signature[:]))
+ hdr.Add(HeaderKeyHash, hex.EncodeToString(l.KeyHash[:]))
+ return headerToBytes(hdr)
+}
+
+// SignedTreeHeadFromHTTP parses a signed tree head from HTTP key-value pairs
+func SignedTreeHeadFromHTTP(buf []byte) (*SignedTreeHead, error) {
+ hdr, err := headerFromBuf(buf)
+ if err != nil {
+ return nil, fmt.Errorf("headerFromBuf(): %v", err)
+ }
+
+ // TreeHead
+ var sth SignedTreeHead
+ sth.Timestamp, err = strconv.ParseUint(hdr.Get(HeaderTimestamp), 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid timestamp: %v", err)
+ }
+ sth.TreeSize, err = strconv.ParseUint(hdr.Get(HeaderTreeSize), 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid tree size: %v", err)
+ }
+ if err := decodeHex(hdr.Get(HeaderRootHash), sth.RootHash[:]); err != nil {
+ return nil, fmt.Errorf("decodeHex(): %v", err)
+ }
+
+ // SigIdent
+ signatures := hdr.Values(HeaderSignature)
+ keyHashes := hdr.Values(HeaderKeyHash)
+ if len(signatures) == 0 {
+ return nil, fmt.Errorf("no signer")
+ }
+ if len(signatures) != len(keyHashes) {
+ return nil, fmt.Errorf("mismatched signature-signer count")
+ }
+ for i := 0; i < len(signatures); i++ {
+ var sigident SigIdent
+ if err := decodeHex(signatures[i], sigident.Signature[:]); err != nil {
+ return nil, fmt.Errorf("decodeHex(): %v", err)
+ }
+ if err := decodeHex(keyHashes[i], sigident.KeyHash[:]); err != nil {
+ return nil, fmt.Errorf("decodeHex(): %v", err)
+ }
+ sth.SigIdent = append(sth.SigIdent, sigident)
+ }
+ return &sth, nil
+}
+
+// ConsistencyProofFromHTTP parses a consistency proof from HTTP key-value pairs
+func ConsistencyProofFromHTTP(buf []byte) (*ConsistencyProof, error) {
+ return nil, nil // TODO
+}
+
+// InclusionProofFromHTTP parses an inclusion proof from HTTP key-value pairs
+func InclusionProofFromHTTP(buf []byte) (*InclusionProof, error) {
+ return nil, nil // TODO
+}
+
+// LeavesFromHTTP parses a list of leaves from HTTP key-value pairs
+func LeavesFromHTTP(buf []byte) ([]*Leaf, error) {
+ return nil, nil // TODO
+}
+
+// headerFromBuf parses ST log HTTP header key-value pairs from a response body
+func headerFromBuf(buf []byte) (http.Header, error) {
+ hdr := http.Header{}
+ lines := strings.Split(string(buf), "\r\n")
+ lines = lines[:len(lines)-1] // skip the final empty line
+ for _, line := range lines {
+ split := strings.Split(line, ":")
+ if len(split) != 2 {
+ return nil, fmt.Errorf("invalid ST log HTTP header: %s", line)
+ }
+ if !strings.HasPrefix(strings.ToLower(split[0]), HeaderPrefix) {
+ return nil, fmt.Errorf("invalid ST log HTTP header prefix: %s", line)
+ }
+ hdr.Add(split[0], strings.TrimSpace(split[1]))
+ }
+ return hdr, nil
+}
+
+// decodeHex decodes a hex-encoded string into a fixed-size output slice
+func decodeHex(str string, out []byte) error {
+ buf, err := hex.DecodeString(str)
+ if err != nil {
+ return fmt.Errorf("hex.DecodeString(): %v", err)
+ }
+ if len(buf) != len(out) {
+ return fmt.Errorf("invalid length: %v", len(buf))
+ }
+ copy(out, buf)
+ return nil
+}
+
+// headerToBytes encodes a header as HTTP key-value pairs
+func headerToBytes(hdr http.Header) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ if err := hdr.Write(buf); err != nil {
+ return nil, fmt.Errorf("hdr.Write(): %v", err) // should not happen
+ }
+ return buf.Bytes(), nil
+}