diff options
| -rw-r--r-- | handler.go | 35 | ||||
| -rw-r--r-- | reqres.go | 17 | ||||
| -rwxr-xr-x | server/testdata/cmd/get-sth | 9 | ||||
| -rw-r--r-- | type.go | 49 | ||||
| -rw-r--r-- | x509.go | 16 | 
5 files changed, 122 insertions, 4 deletions
| @@ -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  } @@ -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 @@ -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))  } @@ -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 +} | 
