From d752d967335e1418f27e03e0389b01178b28f232 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Wed, 28 Oct 2020 13:38:39 +0100 Subject: added signed tree head and get-sth code path --- handler.go | 35 ++++++++++++++++++++++++++++++-- reqres.go | 17 +++++++++++++++- server/testdata/cmd/get-sth | 9 +++++++++ type.go | 49 ++++++++++++++++++++++++++++++++++++++++++++- x509.go | 16 +++++++++++++++ 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100755 server/testdata/cmd/get-sth diff --git a/handler.go b/handler.go index 2a23dbb..ee1eae6 100644 --- a/handler.go +++ b/handler.go @@ -9,6 +9,7 @@ import ( "github.com/golang/glog" "github.com/google/trillian" + "github.com/google/trillian/types" ) // appHandler implements the http.Handler interface, and contains a reference @@ -177,7 +178,37 @@ func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter } // 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, _ *http.Request) (int, error) { glog.Info("in getSth") - return http.StatusOK, nil // TODO + 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) + } + if trillianResponse.SignedLogRoot == nil { + return http.StatusInternalServerError, fmt.Errorf("Trillian returned no tree head") + } + + var lr types.LogRootV1 + if err := lr.UnmarshalBinary(trillianResponse.SignedLogRoot.GetLogRoot()); err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed unmarshaling tree head: %v", err) + } + + th := NewTreeHeadV1(uint64(lr.TimestampNanos / 1000 / 1000), uint64(lr.TreeSize), lr.RootHash) + 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 := NewGetSthResponse(sth) + if err != nil { + return http.StatusInternalServerError, fmt.Errorf("failed creating GetSthResponse: %v", err) + } + if err := WriteJsonResponse(response, w); err != nil { + return http.StatusInternalServerError, err + } + return http.StatusOK, nil } diff --git a/reqres.go b/reqres.go index fe79d51..e223fd9 100644 --- a/reqres.go +++ b/reqres.go @@ -55,11 +55,16 @@ type GetProofByHashResponse struct { InclusionProof string `json:"inclusion_proof"` // base64-encoded StItem } -// GetAnchorsResponse +// GetAnchorsResponse is an assembled get-anchor response type GetAnchorsResponse struct { Certificates []string `json:"certificates"` } +// GetSthResponse is an assembled get-sth response +type GetSthResponse struct { + SignedTreeHead string `json:"sth"` // base64-encoded StItem +} + // NewAddEntryRequest parses and sanitizes the JSON-encoded add-entry // parameters from an incoming HTTP post. The resulting AddEntryRequest is // well-formed, but not necessarily trusted (further sanitization is needed). @@ -189,6 +194,16 @@ func NewGetAnchorsResponse(anchors []*x509.Certificate) GetAnchorsResponse { return GetAnchorsResponse{Certificates: certificates} } +func NewGetSthResponse(sth StItem) (GetSthResponse, error) { + b, err := tls.Marshal(sth) + if err != nil { + return GetSthResponse{}, fmt.Errorf("tls marshal failed: %v", err) + } + return GetSthResponse{ + SignedTreeHead: base64.StdEncoding.EncodeToString(b), + }, nil +} + // VerifyAddEntryRequest determines whether a well-formed AddEntryRequest should // be inserted into the log. The corresponding leaf and appendix is returned. func VerifyAddEntryRequest(ld *LogParameters, r AddEntryRequest) ([]byte, []byte, error) { diff --git a/server/testdata/cmd/get-sth b/server/testdata/cmd/get-sth new file mode 100755 index 0000000..9060633 --- /dev/null +++ b/server/testdata/cmd/get-sth @@ -0,0 +1,9 @@ +#!/bin/bash + +set -eo pipefail +source config + +info "fetching signed tree head" +curl -G $base_url/get-sth +newline +# TODO: try decoding and verifying signature diff --git a/type.go b/type.go index 726b215..be2571e 100644 --- a/type.go +++ b/type.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/base64" + "time" "github.com/google/certificate-transparency-go/tls" "github.com/google/trillian" @@ -25,10 +26,24 @@ const ( // StItem references a versioned item based on a given format specifier. type StItem struct { Format StFormat `tls:"maxval:65535"` + SignedTreeHeadV1 *SignedTreeHeadV1 `tls:"selector:Format,val:1"` SignedDebugInfoV1 *SignedDebugInfoV1 `tls:"selector:Format,val:2"` + // TODO: add consistency proof InclusionProofV1 *InclusionProofV1 `tls:"selector:Format,val:4"` ChecksumV1 *ChecksumV1 `tls:"selector:Format,val:5"` - // TODO: add more items +} + +type SignedTreeHeadV1 struct { + LogId []byte `tls:"minlen:2,maxlen:127"` + TreeHead TreeHeadV1 `tls:minlen:0, maxlen:65535` // what should maxlen be? + Signature []byte `tls:"minlen:0,maxlen:65535"` +} + +type TreeHeadV1 struct { + Timestamp uint64 + TreeSize uint64 + RootHash NodeHash `tls:minlen:32,maxlen:255` + Extension []byte `tls:"minlen:0,maxlen:65535"` } // ChecksumV1 associates a package name with an arbitrary checksum value @@ -60,6 +75,28 @@ type NodeHash struct { Data []byte `tls:"minlen:32,maxlen:255"` } +func NewSignedTreeHeadV1(th TreeHeadV1, logId, signature []byte) StItem { + return StItem{ + Format: StFormatSignedTreeHeadV1, + SignedTreeHeadV1: &SignedTreeHeadV1{ + LogId: logId, + TreeHead: th, + Signature: signature, + }, + } +} + +func NewTreeHeadV1(timestamp, treeSize uint64, rootHash []byte) TreeHeadV1 { + return TreeHeadV1{ + Timestamp: timestamp, + TreeSize: treeSize, + RootHash: NodeHash{ + Data: rootHash, + }, + Extension: nil, + } +} + func NewSignedDebugInfoV1(logId, message, signature []byte) StItem { return StItem{ Format: StFormatSignedDebugInfoV1, @@ -127,11 +164,21 @@ func (i StItem) String() string { return fmt.Sprintf("Format(%s): %s", i.Format, *i.InclusionProofV1) case StFormatSignedDebugInfoV1: return fmt.Sprintf("Format(%s): %s", i.Format, *i.SignedDebugInfoV1) + case StFormatSignedTreeHeadV1: + return fmt.Sprintf("Format(%s): %s", i.Format, *i.SignedTreeHeadV1) default: return fmt.Sprintf("unknown StItem: %s", i.Format) } } +func (th TreeHeadV1) String() string { + return fmt.Sprintf("Timestamp(%s) TreeSize(%d) RootHash(%s)", time.Unix(int64(th.Timestamp/1000), 0), th.TreeSize, base64.StdEncoding.EncodeToString(th.RootHash.Data)) +} + +func (i SignedTreeHeadV1) String() string { + return fmt.Sprintf("LogId(%s) TreeHead(%s) Signature(%s)", base64.StdEncoding.EncodeToString(i.LogId), i.TreeHead, base64.StdEncoding.EncodeToString(i.Signature)) +} + func (i SignedDebugInfoV1) String() string { return fmt.Sprintf("LogId(%s) Message(%s) Signature(%s)", base64.StdEncoding.EncodeToString(i.LogId), string(i.Message), base64.StdEncoding.EncodeToString(i.Signature)) } diff --git a/x509.go b/x509.go index 1e443a1..841b477 100644 --- a/x509.go +++ b/x509.go @@ -11,6 +11,8 @@ import ( "crypto/x509" "encoding/pem" "io/ioutil" + + "github.com/google/certificate-transparency-go/tls" ) // LoadTrustAnchors loads a list of PEM-encoded certificates from file @@ -121,3 +123,17 @@ func GenV1SDI(ld *LogParameters, leaf []byte) (StItem, error) { } return NewSignedDebugInfoV1(ld.LogId, []byte("reserved"), sig), nil } + +func GenV1STH(ld *LogParameters, th TreeHeadV1) (StItem, error) { + serialized, err := tls.Marshal(th) + if err != nil { + return StItem{}, fmt.Errorf("failed tls marshaling tree head: %v", err) + } + + // Note that ed25519 does not use the passed io.Reader + sig, err := ld.Signer.Sign(rand.Reader, serialized, crypto.Hash(0)) + if err != nil { + return StItem{}, fmt.Errorf("ed25519 signature failed: %v", err) + } + return NewSignedTreeHeadV1(th, ld.LogId, sig), nil +} -- cgit v1.2.3