diff options
| -rw-r--r-- | cmd/sigsum_log_go/main.go | 24 | ||||
| -rw-r--r-- | cmd/tmp/cosign/main.go | 43 | ||||
| -rw-r--r-- | cmd/tmp/dns/main.go | 8 | ||||
| -rw-r--r-- | cmd/tmp/keygen/main.go | 4 | ||||
| -rw-r--r-- | cmd/tmp/submit/main.go | 12 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | issues/fix-strict-hex-parsing.md | 10 | ||||
| -rw-r--r-- | pkg/db/client.go | 17 | ||||
| -rw-r--r-- | pkg/db/mocks/client.go (renamed from pkg/mocks/sigsum_trillian_client.go) | 15 | ||||
| -rw-r--r-- | pkg/db/mocks/trillian.go (renamed from pkg/mocks/trillian_log_client.go) | 0 | ||||
| -rw-r--r-- | pkg/db/trillian.go (renamed from pkg/trillian/client.go) | 67 | ||||
| -rw-r--r-- | pkg/db/trillian_test.go (renamed from pkg/trillian/client_test.go) | 93 | ||||
| -rw-r--r-- | pkg/dns/dns.go | 11 | ||||
| -rw-r--r-- | pkg/dns/mocks/dns.go (renamed from pkg/mocks/sigsum_dns.go) | 3 | ||||
| -rw-r--r-- | pkg/instance/experimental.go (renamed from pkg/instance/experimental_endpoint.go) | 2 | ||||
| -rw-r--r-- | pkg/instance/handler.go (renamed from pkg/instance/endpoint.go) | 63 | ||||
| -rw-r--r-- | pkg/instance/handler_test.go (renamed from pkg/instance/endpoint_test.go) | 271 | ||||
| -rw-r--r-- | pkg/instance/instance.go | 104 | ||||
| -rw-r--r-- | pkg/instance/instance_test.go | 98 | ||||
| -rw-r--r-- | pkg/state/mocks/signer.go (renamed from pkg/mocks/crypto.go) | 4 | ||||
| -rw-r--r-- | pkg/state/mocks/state_manager.go (renamed from pkg/mocks/sigsum_state_manager.go) | 4 | ||||
| -rw-r--r-- | pkg/state/single.go | 156 | ||||
| -rw-r--r-- | pkg/state/single_test.go (renamed from pkg/state/state_manager_test.go) | 233 | ||||
| -rw-r--r-- | pkg/state/state_manager.go | 148 | ||||
| -rw-r--r-- | pkg/trillian/util.go | 33 | ||||
| -rw-r--r-- | pkg/types/ascii.go | 399 | ||||
| -rw-r--r-- | pkg/types/ascii_test.go | 438 | ||||
| -rw-r--r-- | pkg/types/trunnel.go | 63 | ||||
| -rw-r--r-- | pkg/types/trunnel_test.go | 116 | ||||
| -rw-r--r-- | pkg/types/types.go | 138 | ||||
| -rw-r--r-- | pkg/types/types_test.go | 58 | ||||
| -rw-r--r-- | pkg/types/util.go | 21 | 
33 files changed, 711 insertions, 1949 deletions
| diff --git a/cmd/sigsum_log_go/main.go b/cmd/sigsum_log_go/main.go index 0c1035b..acda9d6 100644 --- a/cmd/sigsum_log_go/main.go +++ b/cmd/sigsum_log_go/main.go @@ -21,11 +21,11 @@ import (  	"github.com/prometheus/client_golang/prometheus/promhttp"  	"google.golang.org/grpc" -	sigsum "git.sigsum.org/sigsum-log-go/pkg/instance" -	"git.sigsum.org/sigsum-log-go/pkg/state" -	trillianWrapper "git.sigsum.org/sigsum-log-go/pkg/trillian" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db"  	"git.sigsum.org/sigsum-log-go/pkg/dns" +	"git.sigsum.org/sigsum-log-go/pkg/instance" +	"git.sigsum.org/sigsum-log-go/pkg/state"  )  var ( @@ -90,8 +90,8 @@ func main() {  }  // SetupInstance sets up a new sigsum-log-go instance from flags -func setupInstanceFromFlags() (*sigsum.Instance, error) { -	var i sigsum.Instance +func setupInstanceFromFlags() (*instance.Instance, error) { +	var i instance.Instance  	var err error  	// Setup log configuration @@ -119,7 +119,7 @@ func setupInstanceFromFlags() (*sigsum.Instance, error) {  	if err != nil {  		return nil, fmt.Errorf("Dial: %v", err)  	} -	i.Client = &trillianWrapper.TrillianClient{ +	i.Client = &db.TrillianClient{  		TreeID: i.TreeID,  		GRPC:   trillian.NewTrillianLogClient(conn),  	} @@ -157,8 +157,8 @@ func newLogIdentity(key string) (crypto.Signer, string, error) {  }  // newWitnessMap creates a new map of trusted witnesses -func newWitnessMap(witnesses string) (map[[types.HashSize]byte][types.VerificationKeySize]byte, error) { -	w := make(map[[types.HashSize]byte][types.VerificationKeySize]byte) +func newWitnessMap(witnesses string) (map[types.Hash]types.PublicKey, error) { +	w := make(map[types.Hash]types.PublicKey)  	if len(witnesses) > 0 {  		for _, witness := range strings.Split(witnesses, ",") {  			b, err := hex.DecodeString(witness) @@ -166,11 +166,11 @@ func newWitnessMap(witnesses string) (map[[types.HashSize]byte][types.Verificati  				return nil, fmt.Errorf("DecodeString: %v", err)  			} -			var vk [types.VerificationKeySize]byte -			if n := copy(vk[:], b); n != types.VerificationKeySize { +			var vk types.PublicKey +			if n := copy(vk[:], b); n != types.PublicKeySize {  				return nil, fmt.Errorf("Invalid verification key size: %v", n)  			} -			w[*types.Hash(vk[:])] = vk +			w[*types.HashFn(vk[:])] = vk  		}  	}  	return w, nil diff --git a/cmd/tmp/cosign/main.go b/cmd/tmp/cosign/main.go index cb39355..122241f 100644 --- a/cmd/tmp/cosign/main.go +++ b/cmd/tmp/cosign/main.go @@ -3,17 +3,18 @@ package main  import (  	"bytes"  	"crypto/ed25519" -	"encoding/hex"  	"flag"  	"fmt"  	"log"  	"net/http" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/hex" +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  )  var ( -	url    = flag.String("url", "http://localhost:6965/sigsum/v0", "base url") +	url    = flag.String("url", "http://localhost:6965/testonly/sigsum/v0", "base url")  	sk     = flag.String("sk", "e1d7c494dacb0ddf809a17e4528b01f584af22e3766fa740ec52a1711c59500d711090dd2286040b50961b0fe09f58aa665ccee5cb7ee042d819f18f6ab5046b", "witness secret key (hex)")  	log_vk = flag.String("log_vk", "cc0e7294a9d002c33aaa828efba6622ab1ce8ebdb8a795902555c2813133cfe8", "log public key (hex)")  ) @@ -21,14 +22,14 @@ var (  func main() {  	flag.Parse() -	log_vk, err := hex.DecodeString(*log_vk) +	log_vk, err := hex.Deserialize(*log_vk)  	if err != nil { -		log.Fatalf("DecodeString: %v", err) +		log.Fatalf("Deserialize: %v", err)  	} -	priv, err := hex.DecodeString(*sk) +	priv, err := hex.Deserialize(*sk)  	if err != nil { -		log.Fatalf("DecodeString: %v", err) +		log.Fatal(err)  	}  	sk := ed25519.PrivateKey(priv)  	vk := sk.Public().(ed25519.PublicKey) @@ -36,30 +37,32 @@ func main() {  	rsp, err := http.Get(*url + "/get-tree-head-to-sign")  	if err != nil { -		log.Fatalf("Get: %v", err) +		log.Fatal(err)  	}  	var sth types.SignedTreeHead -	if err := sth.UnmarshalASCII(rsp.Body); err != nil { -		log.Fatalf("UnmarshalASCII: %v", err) +	if err := sth.FromASCII(rsp.Body); err != nil { +		log.Fatal(err)  	} -	sth.TreeHead.KeyHash = types.Hash(log_vk)  	fmt.Printf("%+v\n\n", sth) -	msg := sth.TreeHead.Marshal() -	sig := ed25519.Sign(sk, msg) -	sigident := &types.SigIdent{ -		KeyHash:   types.Hash(vk[:]), -		Signature: &[types.SignatureSize]byte{}, +	namespace := types.HashFn(log_vk) +	witSTH, err := sth.TreeHead.Sign(sk, namespace) +	if err != nil { +		log.Fatal(err)  	} -	copy(sigident.Signature[:], sig) +	req := requests.Cosignature{ +		KeyHash:     *types.HashFn(vk[:]), +		Cosignature: witSTH.Signature, +	}  	buf := bytes.NewBuffer(nil) -	if err := sigident.MarshalASCII(buf); err != nil { -		log.Fatalf("MarshalASCII: %v", err) +	if err := req.ToASCII(buf); err != nil { +		log.Fatal(err)  	} +  	rsp, err = http.Post(*url+"/add-cosignature", "type/sigsum", buf)  	if err != nil { -		log.Fatalf("Post: %v", err) +		log.Fatal(err)  	}  	fmt.Printf("Status: %v\n", rsp.StatusCode)  } diff --git a/cmd/tmp/dns/main.go b/cmd/tmp/dns/main.go index b493f15..5f4e5bf 100644 --- a/cmd/tmp/dns/main.go +++ b/cmd/tmp/dns/main.go @@ -2,13 +2,13 @@ package main  import (  	"context" -	"encoding/hex"  	"flag"  	"fmt"  	"log" +	"git.sigsum.org/sigsum-lib-go/pkg/hex" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"git.sigsum.org/sigsum-log-go/pkg/dns" -	"git.sigsum.org/sigsum-log-go/pkg/types"  )  var ( @@ -19,7 +19,7 @@ var (  func main() {  	flag.Parse() -	var key [types.VerificationKeySize]byte +	var key types.PublicKey  	mustDecodeHex(*vk, key[:])  	vf := dns.NewDefaultResolver() @@ -31,7 +31,7 @@ func main() {  }  func mustDecodeHex(s string, buf []byte) { -	b, err := hex.DecodeString(s) +	b, err := hex.Deserialize(s)  	if err != nil {  		log.Fatal(err)  	} diff --git a/cmd/tmp/keygen/main.go b/cmd/tmp/keygen/main.go index c5f60fd..c381022 100644 --- a/cmd/tmp/keygen/main.go +++ b/cmd/tmp/keygen/main.go @@ -6,7 +6,7 @@ import (  	"fmt"  	"log" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  )  func main() { @@ -16,5 +16,5 @@ func main() {  	}  	fmt.Printf("sk: %x\n", sk[:])  	fmt.Printf("vk: %x\n", vk[:]) -	fmt.Printf("kh: %x\n", types.Hash(vk[:])[:]) +	fmt.Printf("kh: %x\n", types.HashFn(vk[:])[:])  } diff --git a/cmd/tmp/submit/main.go b/cmd/tmp/submit/main.go index 2b8050c..f29b168 100644 --- a/cmd/tmp/submit/main.go +++ b/cmd/tmp/submit/main.go @@ -10,7 +10,7 @@ import (  	"fmt"  	"log" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  )  var ( @@ -18,7 +18,7 @@ var (  	checksum   = flag.String("checksum", "", "checksum (hex)")  	sk         = flag.String("sk", "", "secret key (hex)")  	domainHint = flag.String("domain_hint", "example.com", "domain hint (string)") -	base_url   = flag.String("base_url", "localhost:6965", "base url (string)") +	base_url   = flag.String("base_url", "localhost:6965/testonly", "base url (string)")  )  func main() { @@ -28,18 +28,18 @@ func main() {  	var priv ed25519.PrivateKey = ed25519.PrivateKey(privBuf[:])  	mustDecodeHex(*sk, priv[:]) -	var c [types.HashSize]byte +	var c types.Hash  	if *checksum != "" {  		mustDecodeHex(*checksum, c[:])  	} else {  		mustPutRandom(c[:])  	} -	msg := types.Message{ +	msg := types.Statement{  		ShardHint: *shardHint, -		Checksum:  &c, +		Checksum:  c,  	} -	sig := ed25519.Sign(priv, msg.Marshal()) +	sig := ed25519.Sign(priv, msg.ToBinary())  	fmt.Printf("echo \"shard_hint=%d\nchecksum=%x\nsignature=%x\nverification_key=%x\ndomain_hint=%s\" | curl --data-binary @- %s/sigsum/v0/add-leaf\n",  		msg.ShardHint, @@ -3,11 +3,11 @@ module git.sigsum.org/sigsum-log-go  go 1.14  require ( +	git.sigsum.org/sigsum-lib-go v0.0.1  	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b  	github.com/golang/mock v1.4.4  	github.com/google/certificate-transparency-go v1.1.1  	github.com/google/trillian v1.3.13  	github.com/prometheus/client_golang v1.9.0 -	golang.org/x/net v0.0.0-20200625001655-4c5254603344  	google.golang.org/grpc v1.36.0  ) @@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl  cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=  contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=  dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.sigsum.org/sigsum-lib-go v0.0.1 h1:Rn+rMPAxVPf3Pqbd82htSjbBBXPT65A8COpgIrsCo2s= +git.sigsum.org/sigsum-lib-go v0.0.1/go.mod h1:hioRNUsIcepuwtEeXSQcw1JIQ4t0+u4egbh6VgSQeS4=  github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=  github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=  github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= diff --git a/issues/fix-strict-hex-parsing.md b/issues/fix-strict-hex-parsing.md deleted file mode 100644 index bab188e..0000000 --- a/issues/fix-strict-hex-parsing.md +++ /dev/null @@ -1,10 +0,0 @@ -**Title:** Fix strict hex parsing </br> -**Date:** 2021-12-09 </br> - -# Summary -Fix so that sigsum-log-go is strict about lower-case hex parsing. - -# Description -The current sigsum-log-go implementation uses "encoding/hex" which accepts -upper-case and lower-case hex.  This is a violation of the Sigsum API -specification and needs to be fixed: upper-case hex must be rejected. diff --git a/pkg/db/client.go b/pkg/db/client.go new file mode 100644 index 0000000..090dfff --- /dev/null +++ b/pkg/db/client.go @@ -0,0 +1,17 @@ +package db + +import ( +	"context" + +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +) + +// Client is an interface that interacts with a log's database backend +type Client interface { +	AddLeaf(context.Context, *requests.Leaf) error +	GetTreeHead(context.Context) (*types.TreeHead, error) +	GetConsistencyProof(context.Context, *requests.ConsistencyProof) (*types.ConsistencyProof, error) +	GetInclusionProof(context.Context, *requests.InclusionProof) (*types.InclusionProof, error) +	GetLeaves(context.Context, *requests.Leaves) (*types.Leaves, error) +} diff --git a/pkg/mocks/sigsum_trillian_client.go b/pkg/db/mocks/client.go index 3397237..182869f 100644 --- a/pkg/mocks/sigsum_trillian_client.go +++ b/pkg/db/mocks/client.go @@ -1,5 +1,5 @@  // Code generated by MockGen. DO NOT EDIT. -// Source: git.sigsum.org/sigsum-log-go/pkg/trillian (interfaces: Client) +// Source: git.sigsum.org/sigsum-log-go/pkg/db (interfaces: Client)  // Package mocks is a generated GoMock package.  package mocks @@ -8,8 +8,9 @@ import (  	context "context"  	reflect "reflect" +	requests "git.sigsum.org/sigsum-lib-go/pkg/requests" +	types "git.sigsum.org/sigsum-lib-go/pkg/types"  	gomock "github.com/golang/mock/gomock" -	types "git.sigsum.org/sigsum-log-go/pkg/types"  )  // MockClient is a mock of Client interface. @@ -36,7 +37,7 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {  }  // AddLeaf mocks base method. -func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *types.LeafRequest) error { +func (m *MockClient) AddLeaf(arg0 context.Context, arg1 *requests.Leaf) error {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "AddLeaf", arg0, arg1)  	ret0, _ := ret[0].(error) @@ -50,7 +51,7 @@ func (mr *MockClientMockRecorder) AddLeaf(arg0, arg1 interface{}) *gomock.Call {  }  // GetConsistencyProof mocks base method. -func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { +func (m *MockClient) GetConsistencyProof(arg0 context.Context, arg1 *requests.ConsistencyProof) (*types.ConsistencyProof, error) {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "GetConsistencyProof", arg0, arg1)  	ret0, _ := ret[0].(*types.ConsistencyProof) @@ -65,7 +66,7 @@ func (mr *MockClientMockRecorder) GetConsistencyProof(arg0, arg1 interface{}) *g  }  // GetInclusionProof mocks base method. -func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *types.InclusionProofRequest) (*types.InclusionProof, error) { +func (m *MockClient) GetInclusionProof(arg0 context.Context, arg1 *requests.InclusionProof) (*types.InclusionProof, error) {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "GetInclusionProof", arg0, arg1)  	ret0, _ := ret[0].(*types.InclusionProof) @@ -80,10 +81,10 @@ func (mr *MockClientMockRecorder) GetInclusionProof(arg0, arg1 interface{}) *gom  }  // GetLeaves mocks base method. -func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *types.LeavesRequest) (*types.LeafList, error) { +func (m *MockClient) GetLeaves(arg0 context.Context, arg1 *requests.Leaves) (*types.Leaves, error) {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "GetLeaves", arg0, arg1) -	ret0, _ := ret[0].(*types.LeafList) +	ret0, _ := ret[0].(*types.Leaves)  	ret1, _ := ret[1].(error)  	return ret0, ret1  } diff --git a/pkg/mocks/trillian_log_client.go b/pkg/db/mocks/trillian.go index 8aa3a58..8aa3a58 100644 --- a/pkg/mocks/trillian_log_client.go +++ b/pkg/db/mocks/trillian.go diff --git a/pkg/trillian/client.go b/pkg/db/trillian.go index 6fe8ce4..ab57db6 100644 --- a/pkg/trillian/client.go +++ b/pkg/db/trillian.go @@ -1,25 +1,18 @@ -package trillian +package db  import (  	"context"  	"fmt" +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"github.com/golang/glog"  	"github.com/google/trillian" -	ttypes "github.com/google/trillian/types" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	trillianTypes "github.com/google/trillian/types"  	"google.golang.org/grpc/codes"  ) -type Client interface { -	AddLeaf(context.Context, *types.LeafRequest) error -	GetConsistencyProof(context.Context, *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) -	GetTreeHead(context.Context) (*types.TreeHead, error) -	GetInclusionProof(context.Context, *types.InclusionProofRequest) (*types.InclusionProof, error) -	GetLeaves(context.Context, *types.LeavesRequest) (*types.LeafList, error) -} - -// TrillianClient is a wrapper around the Trillian gRPC client. +// TrillianClient implements the Client interface for Trillian's gRPC backend  type TrillianClient struct {  	// TreeID is a Merkle tree identifier that Trillian uses  	TreeID int64 @@ -28,17 +21,18 @@ type TrillianClient struct {  	GRPC trillian.TrillianLogClient  } -func (c *TrillianClient) AddLeaf(ctx context.Context, req *types.LeafRequest) error { +func (c *TrillianClient) AddLeaf(ctx context.Context, req *requests.Leaf) error {  	leaf := types.Leaf{ -		Message: req.Message, -		SigIdent: types.SigIdent{ -			Signature: req.Signature, -			KeyHash:   types.Hash(req.VerificationKey[:]), +		Statement: types.Statement{ +			ShardHint: req.ShardHint, +			Checksum:  req.Checksum,  		}, +		Signature: req.Signature, +		KeyHash:   *types.HashFn(req.VerificationKey[:]),  	} -	serialized := leaf.Marshal() +	serialized := leaf.ToBinary() -	glog.V(3).Infof("queueing leaf request: %x", types.HashLeaf(serialized)) +	glog.V(3).Infof("queueing leaf request: %x", types.LeafHash(serialized))  	rsp, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{  		LogId: c.TreeID,  		Leaf: &trillian.LogLeaf{ @@ -76,7 +70,7 @@ func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, erro  	if rsp.SignedLogRoot.LogRoot == nil {  		return nil, fmt.Errorf("no log root")  	} -	var r ttypes.LogRootV1 +	var r trillianTypes.LogRootV1  	if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil {  		return nil, fmt.Errorf("no log root: unmarshal failed: %v", err)  	} @@ -86,7 +80,7 @@ func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, erro  	return treeHeadFromLogRoot(&r), nil  } -func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { +func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *requests.ConsistencyProof) (*types.ConsistencyProof, error) {  	rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{  		LogId:          c.TreeID,  		FirstTreeSize:  int64(req.OldSize), @@ -115,7 +109,7 @@ func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.Con  	}, nil  } -func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { +func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *requests.InclusionProof) (*types.InclusionProof, error) {  	rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{  		LogId:           c.TreeID,  		LeafHash:        req.LeafHash[:], @@ -146,7 +140,7 @@ func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.Inclu  	}, nil  } -func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { +func (c *TrillianClient) GetLeaves(ctx context.Context, req *requests.Leaves) (*types.Leaves, error) {  	rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{  		LogId:      c.TreeID,  		StartIndex: int64(req.StartSize), @@ -161,7 +155,7 @@ func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest  	if got, want := len(rsp.Leaves), int(req.EndSize-req.StartSize+1); got != want {  		return nil, fmt.Errorf("unexpected number of leaves: %d", got)  	} -	var list types.LeafList +	var list types.Leaves = make([]types.Leaf, 0, len(rsp.Leaves))  	for i, leaf := range rsp.Leaves {  		leafIndex := int64(req.StartSize + uint64(i))  		if leafIndex != leaf.LeafIndex { @@ -169,10 +163,31 @@ func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest  		}  		var l types.Leaf -		if err := l.Unmarshal(leaf.LeafValue); err != nil { +		if err := l.FromBinary(leaf.LeafValue); err != nil {  			return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err)  		} -		list = append(list[:], &l) +		list = append(list[:], l)  	}  	return &list, nil  } + +func treeHeadFromLogRoot(lr *trillianTypes.LogRootV1) *types.TreeHead { +	th := types.TreeHead{ +		Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), +		TreeSize:  uint64(lr.TreeSize), +	} +	copy(th.RootHash[:], lr.RootHash) +	return &th +} + +func nodePathFromHashes(hashes [][]byte) ([]types.Hash, error) { +	path := make([]types.Hash, len(hashes)) +	for i := 0; i < len(hashes); i++ { +		if len(hashes[i]) != types.HashSize { +			return nil, fmt.Errorf("unexpected hash length: %v", len(hashes[i])) +		} + +		copy(path[i][:], hashes[i]) +	} +	return path, nil +} diff --git a/pkg/trillian/client_test.go b/pkg/db/trillian_test.go index 143c411..a33458f 100644 --- a/pkg/trillian/client_test.go +++ b/pkg/db/trillian_test.go @@ -1,4 +1,4 @@ -package trillian +package db  import (  	"context" @@ -6,28 +6,29 @@ import (  	"reflect"  	"testing" +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db/mocks"  	"github.com/golang/mock/gomock"  	"github.com/google/trillian"  	ttypes "github.com/google/trillian/types" -	"git.sigsum.org/sigsum-log-go/pkg/mocks" -	"git.sigsum.org/sigsum-log-go/pkg/types"  	"google.golang.org/grpc/codes"  	"google.golang.org/grpc/status"  )  func TestAddLeaf(t *testing.T) { -	req := &types.LeafRequest{ -		Message: types.Message{ +	req := &requests.Leaf{ +		Statement: types.Statement{  			ShardHint: 0, -			Checksum:  &[types.HashSize]byte{}, +			Checksum:  types.Hash{},  		}, -		Signature:       &[types.SignatureSize]byte{}, -		VerificationKey: &[types.VerificationKeySize]byte{}, +		Signature:       types.Signature{}, +		VerificationKey: types.PublicKey{},  		DomainHint:      "example.com",  	}  	for _, table := range []struct {  		description string -		req         *types.LeafRequest +		req         *requests.Leaf  		rsp         *trillian.QueueLeafResponse  		err         error  		wantErr     bool @@ -55,7 +56,7 @@ func TestAddLeaf(t *testing.T) {  			rsp: &trillian.QueueLeafResponse{  				QueuedLeaf: &trillian.QueuedLogLeaf{  					Leaf: &trillian.LogLeaf{ -						LeafValue: req.Message.Marshal(), +						LeafValue: []byte{0}, // does not matter for test  					},  					Status: status.New(codes.AlreadyExists, "duplicate").Proto(),  				}, @@ -68,7 +69,7 @@ func TestAddLeaf(t *testing.T) {  			rsp: &trillian.QueueLeafResponse{  				QueuedLeaf: &trillian.QueuedLogLeaf{  					Leaf: &trillian.LogLeaf{ -						LeafValue: req.Message.Marshal(), +						LeafValue: []byte{0}, // does not matter for test  					},  					Status: status.New(codes.OK, "ok").Proto(),  				}, @@ -165,7 +166,7 @@ func TestGetTreeHead(t *testing.T) {  			wantTh: &types.TreeHead{  				Timestamp: 1622585623,  				TreeSize:  0, -				RootHash:  &[types.HashSize]byte{}, +				RootHash:  types.Hash{},  			},  		},  	} { @@ -192,13 +193,13 @@ func TestGetTreeHead(t *testing.T) {  }  func TestGetConsistencyProof(t *testing.T) { -	req := &types.ConsistencyProofRequest{ +	req := &requests.ConsistencyProof{  		OldSize: 1,  		NewSize: 3,  	}  	for _, table := range []struct {  		description string -		req         *types.ConsistencyProofRequest +		req         *requests.ConsistencyProof  		rsp         *trillian.GetConsistencyProofResponse  		err         error  		wantErr     bool @@ -258,9 +259,9 @@ func TestGetConsistencyProof(t *testing.T) {  			wantProof: &types.ConsistencyProof{  				OldSize: 1,  				NewSize: 3, -				Path: []*[types.HashSize]byte{ -					&[types.HashSize]byte{}, -					&[types.HashSize]byte{}, +				Path: []types.Hash{ +					types.Hash{}, +					types.Hash{},  				},  			},  		}, @@ -288,13 +289,13 @@ func TestGetConsistencyProof(t *testing.T) {  }  func TestGetInclusionProof(t *testing.T) { -	req := &types.InclusionProofRequest{ +	req := &requests.InclusionProof{  		TreeSize: 4, -		LeafHash: &[types.HashSize]byte{}, +		LeafHash: types.Hash{},  	}  	for _, table := range []struct {  		description string -		req         *types.InclusionProofRequest +		req         *requests.InclusionProof  		rsp         *trillian.GetInclusionProofByHashResponse  		err         error  		wantErr     bool @@ -368,9 +369,9 @@ func TestGetInclusionProof(t *testing.T) {  			wantProof: &types.InclusionProof{  				TreeSize:  4,  				LeafIndex: 1, -				Path: []*[types.HashSize]byte{ -					&[types.HashSize]byte{}, -					&[types.HashSize]byte{}, +				Path: []types.Hash{ +					types.Hash{}, +					types.Hash{},  				},  			},  		}, @@ -398,38 +399,34 @@ func TestGetInclusionProof(t *testing.T) {  }  func TestGetLeaves(t *testing.T) { -	req := &types.LeavesRequest{ +	req := &requests.Leaves{  		StartSize: 1,  		EndSize:   2,  	}  	firstLeaf := &types.Leaf{ -		Message: types.Message{ +		Statement: types.Statement{  			ShardHint: 0, -			Checksum:  &[types.HashSize]byte{}, -		}, -		SigIdent: types.SigIdent{ -			Signature: &[types.SignatureSize]byte{}, -			KeyHash:   &[types.HashSize]byte{}, +			Checksum:  types.Hash{},  		}, +		Signature: types.Signature{}, +		KeyHash:   types.Hash{},  	}  	secondLeaf := &types.Leaf{ -		Message: types.Message{ +		Statement: types.Statement{  			ShardHint: 0, -			Checksum:  &[types.HashSize]byte{}, -		}, -		SigIdent: types.SigIdent{ -			Signature: &[types.SignatureSize]byte{}, -			KeyHash:   &[types.HashSize]byte{}, +			Checksum:  types.Hash{},  		}, +		Signature: types.Signature{}, +		KeyHash:   types.Hash{},  	}  	for _, table := range []struct {  		description string -		req         *types.LeavesRequest +		req         *requests.Leaves  		rsp         *trillian.GetLeavesByRangeResponse  		err         error  		wantErr     bool -		wantLeaves  *types.LeafList +		wantLeaves  *types.Leaves  	}{  		{  			description: "invalid: backend failure", @@ -448,7 +445,7 @@ func TestGetLeaves(t *testing.T) {  			rsp: &trillian.GetLeavesByRangeResponse{  				Leaves: []*trillian.LogLeaf{  					&trillian.LogLeaf{ -						LeafValue: firstLeaf.Marshal(), +						LeafValue: firstLeaf.ToBinary(),  						LeafIndex: 1,  					},  				}, @@ -461,11 +458,11 @@ func TestGetLeaves(t *testing.T) {  			rsp: &trillian.GetLeavesByRangeResponse{  				Leaves: []*trillian.LogLeaf{  					&trillian.LogLeaf{ -						LeafValue: firstLeaf.Marshal(), +						LeafValue: firstLeaf.ToBinary(),  						LeafIndex: 1,  					},  					&trillian.LogLeaf{ -						LeafValue: secondLeaf.Marshal(), +						LeafValue: secondLeaf.ToBinary(),  						LeafIndex: 3,  					},  				}, @@ -478,11 +475,11 @@ func TestGetLeaves(t *testing.T) {  			rsp: &trillian.GetLeavesByRangeResponse{  				Leaves: []*trillian.LogLeaf{  					&trillian.LogLeaf{ -						LeafValue: firstLeaf.Marshal(), +						LeafValue: firstLeaf.ToBinary(),  						LeafIndex: 1,  					},  					&trillian.LogLeaf{ -						LeafValue: secondLeaf.Marshal()[1:], +						LeafValue: secondLeaf.ToBinary()[1:],  						LeafIndex: 2,  					},  				}, @@ -495,18 +492,18 @@ func TestGetLeaves(t *testing.T) {  			rsp: &trillian.GetLeavesByRangeResponse{  				Leaves: []*trillian.LogLeaf{  					&trillian.LogLeaf{ -						LeafValue: firstLeaf.Marshal(), +						LeafValue: firstLeaf.ToBinary(),  						LeafIndex: 1,  					},  					&trillian.LogLeaf{ -						LeafValue: secondLeaf.Marshal(), +						LeafValue: secondLeaf.ToBinary(),  						LeafIndex: 2,  					},  				},  			}, -			wantLeaves: &types.LeafList{ -				firstLeaf, -				secondLeaf, +			wantLeaves: &types.Leaves{ +				*firstLeaf, +				*secondLeaf,  			},  		},  	} { diff --git a/pkg/dns/dns.go b/pkg/dns/dns.go index 7979119..94cbdeb 100644 --- a/pkg/dns/dns.go +++ b/pkg/dns/dns.go @@ -5,14 +5,13 @@ import (  	"fmt"  	"net" -	"encoding/hex" - -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/hex" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  )  // Verifier can verify that a domain name is aware of a public key  type Verifier interface { -	Verify(ctx context.Context, name string, key *[types.VerificationKeySize]byte) error +	Verify(ctx context.Context, name string, key *types.PublicKey) error  }  // DefaultResolver implements the Verifier interface with Go's default resolver @@ -24,13 +23,13 @@ func NewDefaultResolver() Verifier {  	return &DefaultResolver{}  } -func (dr *DefaultResolver) Verify(ctx context.Context, name string, key *[types.VerificationKeySize]byte) error { +func (dr *DefaultResolver) Verify(ctx context.Context, name string, key *types.PublicKey) error {  	rsp, err := dr.resolver.LookupTXT(ctx, name)  	if err != nil {  		return fmt.Errorf("domain name look-up failed: %v", err)  	} -	want := hex.EncodeToString(types.Hash(key[:])[:]) +	want := hex.Serialize(types.HashFn(key[:])[:])  	for _, got := range rsp {  		if got == want {  			return nil diff --git a/pkg/mocks/sigsum_dns.go b/pkg/dns/mocks/dns.go index ede237e..c60082b 100644 --- a/pkg/mocks/sigsum_dns.go +++ b/pkg/dns/mocks/dns.go @@ -8,6 +8,7 @@ import (  	context "context"  	reflect "reflect" +	types "git.sigsum.org/sigsum-lib-go/pkg/types"  	gomock "github.com/golang/mock/gomock"  ) @@ -35,7 +36,7 @@ func (m *MockVerifier) EXPECT() *MockVerifierMockRecorder {  }  // Verify mocks base method. -func (m *MockVerifier) Verify(arg0 context.Context, arg1 string, arg2 *[32]byte) error { +func (m *MockVerifier) Verify(arg0 context.Context, arg1 string, arg2 *types.PublicKey) error {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "Verify", arg0, arg1, arg2)  	ret0, _ := ret[0].(error) diff --git a/pkg/instance/experimental_endpoint.go b/pkg/instance/experimental.go index 2986a27..ab81ada 100644 --- a/pkg/instance/experimental_endpoint.go +++ b/pkg/instance/experimental.go @@ -11,7 +11,7 @@ import (  	"fmt"  	"net/http" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"github.com/golang/glog"  ) diff --git a/pkg/instance/endpoint.go b/pkg/instance/handler.go index a6d424d..66a20a5 100644 --- a/pkg/instance/endpoint.go +++ b/pkg/instance/handler.go @@ -2,11 +2,58 @@ package instance  import (  	"context" +	"fmt"  	"net/http" +	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  	"github.com/golang/glog"  ) +// Handler implements the http.Handler interface, and contains a reference +// to a sigsum server instance as well as a function that uses it. +type Handler struct { +	Instance *Instance +	Endpoint types.Endpoint +	Method   string +	Handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) +} + +// Path returns a path that should be configured for this handler +func (h Handler) Path() string { +	if len(h.Instance.Prefix) == 0 { +		return h.Endpoint.Path("", "sigsum", "v0") +	} +	return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0") +} + +// ServeHTTP is part of the http.Handler interface +func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +	// export prometheus metrics +	var now time.Time = time.Now() +	var statusCode int +	defer func() { +		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) +	}() +	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) + +	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) +	defer cancel() + +	if r.Method != a.Method { +		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) +		http.Error(w, "", http.StatusMethodNotAllowed) +		return +	} + +	statusCode, err := a.Handler(ctx, a.Instance, w, r) +	if err != nil { +		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) +		http.Error(w, fmt.Sprintf("Error=%s\n", err.Error()), statusCode) +	} +} +  func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {  	glog.V(3).Info("handling add-entry request")  	req, err := i.leafRequestFromHTTP(ctx, r) @@ -25,8 +72,8 @@ func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *  	if err != nil {  		return http.StatusBadRequest, err  	} -	vk := i.Witnesses[*req.KeyHash] -	if err := i.Stateman.AddCosignature(ctx, &vk, req.Signature); err != nil { +	vk := i.Witnesses[req.KeyHash] +	if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil {  		return http.StatusBadRequest, err  	}  	return http.StatusOK, nil @@ -38,7 +85,7 @@ func getTreeHeadLatest(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := sth.MarshalASCII(w); err != nil { +	if err := sth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -50,7 +97,7 @@ func getTreeHeadToSign(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := sth.MarshalASCII(w); err != nil { +	if err := sth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -62,7 +109,7 @@ func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := cth.MarshalASCII(w); err != nil { +	if err := cth.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -79,7 +126,7 @@ func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := proof.MarshalASCII(w); err != nil { +	if err := proof.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -96,7 +143,7 @@ func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter,  	if err != nil {  		return http.StatusInternalServerError, err  	} -	if err := proof.MarshalASCII(w); err != nil { +	if err := proof.ToASCII(w); err != nil {  		return http.StatusInternalServerError, err  	}  	return http.StatusOK, nil @@ -114,7 +161,7 @@ func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.  		return http.StatusInternalServerError, err  	}  	for _, leaf := range *leaves { -		if err := leaf.MarshalASCII(w); err != nil { +		if err := leaf.ToASCII(w); err != nil {  			return http.StatusInternalServerError, err  		}  	} diff --git a/pkg/instance/endpoint_test.go b/pkg/instance/handler_test.go index 18a6c27..ba5b60c 100644 --- a/pkg/instance/endpoint_test.go +++ b/pkg/instance/handler_test.go @@ -4,60 +4,137 @@ import (  	"bytes"  	"crypto/ed25519"  	"crypto/rand" -	"encoding/hex"  	"fmt"  	"io"  	"net/http"  	"net/http/httptest" +	"reflect"  	"testing"  	"time" -	"git.sigsum.org/sigsum-log-go/pkg/mocks" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	mocksDB "git.sigsum.org/sigsum-log-go/pkg/db/mocks" +	mocksDNS "git.sigsum.org/sigsum-log-go/pkg/dns/mocks" +	mocksState "git.sigsum.org/sigsum-log-go/pkg/state/mocks"  	"github.com/golang/mock/gomock"  )  var ( -	testWitVK  = [types.VerificationKeySize]byte{} +	testWitVK  = types.PublicKey{}  	testConfig = Config{ -		LogID:      hex.EncodeToString(types.Hash([]byte("logid"))[:]), +		LogID:      fmt.Sprintf("%x", types.HashFn([]byte("logid"))[:]),  		TreeID:     0,  		Prefix:     "testonly",  		MaxRange:   3,  		Deadline:   10,  		Interval:   10,  		ShardStart: 10, -		Witnesses: map[[types.HashSize]byte][types.VerificationKeySize]byte{ -			*types.Hash(testWitVK[:]): testWitVK, +		Witnesses: map[types.Hash]types.PublicKey{ +			*types.HashFn(testWitVK[:]): testWitVK,  		},  	}  	testSTH = &types.SignedTreeHead{  		TreeHead: types.TreeHead{  			Timestamp: 0,  			TreeSize:  0, -			RootHash:  types.Hash(nil), +			RootHash:  *types.HashFn([]byte("root hash")),  		}, -		Signature: &[types.SignatureSize]byte{}, +		Signature: types.Signature{},  	}  	testCTH = &types.CosignedTreeHead{  		SignedTreeHead: *testSTH, -		SigIdent: []*types.SigIdent{ -			&types.SigIdent{ -				KeyHash:   &[types.HashSize]byte{}, -				Signature: &[types.SignatureSize]byte{}, -			}, -		}, +		Cosignature:    []types.Signature{types.Signature{}}, +		KeyHash:        []types.Hash{types.Hash{}},  	}  ) -func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { +// TestHandlers check that the expected handlers are configured +func TestHandlers(t *testing.T) { +	endpoints := map[types.Endpoint]bool{ +		types.EndpointAddLeaf:             false, +		types.EndpointAddCosignature:      false, +		types.EndpointGetTreeHeadLatest:   false, +		types.EndpointGetTreeHeadToSign:   false, +		types.EndpointGetTreeHeadCosigned: false, +		types.EndpointGetConsistencyProof: false, +		types.EndpointGetInclusionProof:   false, +		types.EndpointGetLeaves:           false, +		types.Endpoint("get-checkpoint"):  false, +	} +	i := &Instance{ +		Config: testConfig, +	}  	for _, handler := range i.Handlers() { -		if handler.Endpoint == e { -			return handler +		if _, ok := endpoints[handler.Endpoint]; !ok { +			t.Errorf("got unexpected endpoint: %s", handler.Endpoint) +		} +		endpoints[handler.Endpoint] = true +	} +	for endpoint, ok := range endpoints { +		if !ok { +			t.Errorf("endpoint %s is not configured", endpoint) +		} +	} +} + +// TestServeHTTP checks that invalid HTTP methods are rejected +func TestServeHTTP(t *testing.T) { +	i := &Instance{ +		Config: testConfig, +	} +	for _, handler := range i.Handlers() { +		// Prepare invalid HTTP request +		method := http.MethodPost +		if method == handler.Method { +			method = http.MethodGet +		} +		url := handler.Endpoint.Path("http://example.com", i.Prefix) +		req, err := http.NewRequest(method, url, nil) +		if err != nil { +			t.Fatalf("must create HTTP request: %v", err) +		} +		w := httptest.NewRecorder() + +		// Check that it is rejected +		handler.ServeHTTP(w, req) +		if got, want := w.Code, http.StatusMethodNotAllowed; got != want { +			t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) +		} +	} +} + +// TestPath checks that Path works for an endpoint (add-leaf) +func TestPath(t *testing.T) { +	for _, table := range []struct { +		description string +		prefix      string +		want        string +	}{ +		{ +			description: "no prefix", +			want:        "/sigsum/v0/add-leaf", +		}, +		{ +			description: "a prefix", +			prefix:      "test-prefix", +			want:        "/test-prefix/sigsum/v0/add-leaf", +		}, +	} { +		instance := &Instance{ +			Config: Config{ +				Prefix: table.prefix, +			}, +		} +		handler := Handler{ +			Instance: instance, +			Handler:  addLeaf, +			Endpoint: types.EndpointAddLeaf, +			Method:   http.MethodPost, +		} +		if got, want := handler.Path(), table.want; got != want { +			t.Errorf("got path %v but wanted %v", got, want)  		}  	} -	t.Fatalf("must handle endpoint: %v", e) -	return Handler{}  }  func TestAddLeaf(t *testing.T) { @@ -77,29 +154,29 @@ func TestAddLeaf(t *testing.T) {  		},  		{  			description: "invalid: bad request (signature error)", -			ascii:       mustLeafBuffer(t, 10, &[types.HashSize]byte{}, false), +			ascii:       mustLeafBuffer(t, 10, types.Hash{}, false),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: bad request (shard hint is before shard start)", -			ascii:       mustLeafBuffer(t, 9, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, 9, types.Hash{}, true),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: bad request (shard hint is after shard end)", -			ascii:       mustLeafBuffer(t, uint64(time.Now().Unix())+1024, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, uint64(time.Now().Unix())+1024, types.Hash{}, true),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: failed verifying domain hint", -			ascii:       mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:       mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:   true,  			errDNS:      fmt.Errorf("something went wrong"),  			wantCode:    http.StatusBadRequest,  		},  		{  			description:    "invalid: backend failure", -			ascii:          mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:          mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:      true,  			expectTrillian: true,  			errTrillian:    fmt.Errorf("something went wrong"), @@ -107,7 +184,7 @@ func TestAddLeaf(t *testing.T) {  		},  		{  			description:    "valid", -			ascii:          mustLeafBuffer(t, 10, &[types.HashSize]byte{}, true), +			ascii:          mustLeafBuffer(t, 10, types.Hash{}, true),  			expectDNS:      true,  			expectTrillian: true,  			wantCode:       http.StatusOK, @@ -117,11 +194,11 @@ func TestAddLeaf(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			dns := mocks.NewMockVerifier(ctrl) +			dns := mocksDNS.NewMockVerifier(ctrl)  			if table.expectDNS {  				dns.EXPECT().Verify(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.errDNS)  			} -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expectTrillian {  				client.EXPECT().AddLeaf(gomock.Any(), gomock.Any()).Return(table.errTrillian)  			} @@ -150,10 +227,9 @@ func TestAddLeaf(t *testing.T) {  func TestAddCosignature(t *testing.T) {  	buf := func() io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%x%s"+"%s%s%x%s", -			types.Cosignature, types.Delim, make([]byte, types.SignatureSize), types.EOL, -			types.KeyHash, types.Delim, *types.Hash(testWitVK[:]), types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", +			"cosignature", types.Signature{}, +			"key_hash", *types.HashFn(testWitVK[:]),  		))  	}  	for _, table := range []struct { @@ -170,10 +246,9 @@ func TestAddCosignature(t *testing.T) {  		},  		{  			description: "invalid: bad request (unknown witness)", -			ascii: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%x%s"+"%s%s%x%s", -				types.Signature, types.Delim, make([]byte, types.SignatureSize), types.EOL, -				types.KeyHash, types.Delim, *types.Hash(testWitVK[1:]), types.EOL, +			ascii: bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%x\n", +				"cosignature", types.Signature{}, +				"key_hash", *types.HashFn(testWitVK[1:]),  			)),  			wantCode: http.StatusBadRequest,  		}, @@ -195,7 +270,7 @@ func TestAddCosignature(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().AddCosignature(gomock.Any(), gomock.Any(), gomock.Any()).Return(table.err)  			} @@ -246,7 +321,7 @@ func TestGetTreeHeadLatest(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().Latest(gomock.Any()).Return(table.rsp, table.err)  			} @@ -297,7 +372,7 @@ func TestGetTreeToSign(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().ToSign(gomock.Any()).Return(table.rsp, table.err)  			} @@ -348,7 +423,7 @@ func TestGetTreeCosigned(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			stateman := mocks.NewMockStateManager(ctrl) +			stateman := mocksState.NewMockStateManager(ctrl)  			if table.expect {  				stateman.EXPECT().Cosigned(gomock.Any()).Return(table.rsp, table.err)  			} @@ -376,10 +451,9 @@ func TestGetTreeCosigned(t *testing.T) {  func TestGetConsistencyProof(t *testing.T) {  	buf := func(oldSize, newSize int) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%d%s"+"%s%s%d%s", -			types.OldSize, types.Delim, oldSize, types.EOL, -			types.NewSize, types.Delim, newSize, types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", +			"old_size", oldSize, +			"new_size", newSize,  		))  	}  	for _, table := range []struct { @@ -419,8 +493,8 @@ func TestGetConsistencyProof(t *testing.T) {  			rsp: &types.ConsistencyProof{  				OldSize: 1,  				NewSize: 2, -				Path: []*[types.HashSize]byte{ -					types.Hash(nil), +				Path: []types.Hash{ +					*types.HashFn([]byte{}),  				},  			},  			wantCode: http.StatusOK, @@ -430,7 +504,7 @@ func TestGetConsistencyProof(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -457,11 +531,10 @@ func TestGetConsistencyProof(t *testing.T) {  }  func TestGetInclusionProof(t *testing.T) { -	buf := func(hash *[types.HashSize]byte, treeSize int) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%x%s"+"%s%s%d%s", -			types.LeafHash, types.Delim, hash[:], types.EOL, -			types.TreeSize, types.Delim, treeSize, types.EOL, +	buf := func(hash *types.Hash, treeSize int) io.Reader { +		return bytes.NewBufferString(fmt.Sprintf("%s=%x\n%s=%d\n", +			"leaf_hash", hash[:], +			"tree_size", treeSize,  		))  	}  	for _, table := range []struct { @@ -479,25 +552,25 @@ func TestGetInclusionProof(t *testing.T) {  		},  		{  			description: "invalid: bad request (no proof for tree size)", -			ascii:       buf(types.Hash(nil), 1), +			ascii:       buf(types.HashFn([]byte{}), 1),  			wantCode:    http.StatusBadRequest,  		},  		{  			description: "invalid: backend failure", -			ascii:       buf(types.Hash(nil), 2), +			ascii:       buf(types.HashFn([]byte{}), 2),  			expect:      true,  			err:         fmt.Errorf("something went wrong"),  			wantCode:    http.StatusInternalServerError,  		},  		{  			description: "valid", -			ascii:       buf(types.Hash(nil), 2), +			ascii:       buf(types.HashFn([]byte{}), 2),  			expect:      true,  			rsp: &types.InclusionProof{  				TreeSize:  2,  				LeafIndex: 0, -				Path: []*[types.HashSize]byte{ -					types.Hash(nil), +				Path: []types.Hash{ +					*types.HashFn([]byte{}),  				},  			},  			wantCode: http.StatusOK, @@ -507,7 +580,7 @@ func TestGetInclusionProof(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetInclusionProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -535,19 +608,18 @@ func TestGetInclusionProof(t *testing.T) {  func TestGetLeaves(t *testing.T) {  	buf := func(startSize, endSize int64) io.Reader { -		return bytes.NewBufferString(fmt.Sprintf( -			"%s%s%d%s"+"%s%s%d%s", -			types.StartSize, types.Delim, startSize, types.EOL, -			types.EndSize, types.Delim, endSize, types.EOL, +		return bytes.NewBufferString(fmt.Sprintf("%s=%d\n%s=%d\n", +			"start_size", startSize, +			"end_size", endSize,  		))  	}  	for _, table := range []struct {  		description string -		ascii       io.Reader       // buffer used to populate HTTP request -		expect      bool            // set if a mock answer is expected -		rsp         *types.LeafList // list of leaves from Trillian client -		err         error           // error from Trillian client -		wantCode    int             // HTTP status ok +		ascii       io.Reader     // buffer used to populate HTTP request +		expect      bool          // set if a mock answer is expected +		rsp         *types.Leaves // list of leaves from Trillian client +		err         error         // error from Trillian client +		wantCode    int           // HTTP status ok  	}{  		{  			description: "invalid: bad request (parser error)", @@ -570,18 +642,16 @@ func TestGetLeaves(t *testing.T) {  			description: "valid: one more entry than the configured MaxRange",  			ascii:       buf(0, testConfig.MaxRange), // query will be pruned  			expect:      true, -			rsp: func() *types.LeafList { -				var list types.LeafList +			rsp: func() *types.Leaves { +				var list types.Leaves  				for i := int64(0); i < testConfig.MaxRange; i++ { -					list = append(list[:], &types.Leaf{ -						Message: types.Message{ +					list = append(list[:], types.Leaf{ +						Statement: types.Statement{  							ShardHint: 0, -							Checksum:  types.Hash(nil), -						}, -						SigIdent: types.SigIdent{ -							Signature: &[types.SignatureSize]byte{}, -							KeyHash:   types.Hash(nil), +							Checksum:  types.Hash{},  						}, +						Signature: types.Signature{}, +						KeyHash:   types.Hash{},  					})  				}  				return &list @@ -593,7 +663,7 @@ func TestGetLeaves(t *testing.T) {  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksDB.NewMockClient(ctrl)  			if table.expect {  				client.EXPECT().GetLeaves(gomock.Any(), gomock.Any()).Return(table.rsp, table.err)  			} @@ -619,43 +689,48 @@ func TestGetLeaves(t *testing.T) {  				return  			} -			// TODO: check that we got the right leaves back.  It is especially -			// important that we check that we got the right number of leaves. -			// -			// Pseuducode for when we have types.LeafList.UnmarshalASCII() -			// -			//list := &types.LeafList{} -			//if err := list.UnmarshalASCII(w.Body); err != nil { -			//	t.Fatalf("must unmarshal leaf list: %v", err) -			//} -			//if got, want := list, table.rsp; !reflect.DeepEqual(got, want) { -			//	t.Errorf("got leaf list\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -			//} +			list := types.Leaves{} +			if err := list.FromASCII(w.Body); err != nil { +				t.Fatalf("must unmarshal leaf list: %v", err) +			} +			if got, want := &list, table.rsp; !reflect.DeepEqual(got, want) { +				t.Errorf("got leaf list\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) +			}  		}()  	}  } -func mustLeafBuffer(t *testing.T, shardHint uint64, checksum *[types.HashSize]byte, wantSig bool) io.Reader { +func mustHandle(t *testing.T, i Instance, e types.Endpoint) Handler { +	for _, handler := range i.Handlers() { +		if handler.Endpoint == e { +			return handler +		} +	} +	t.Fatalf("must handle endpoint: %v", e) +	return Handler{} +} + +func mustLeafBuffer(t *testing.T, shardHint uint64, checksum types.Hash, wantSig bool) io.Reader {  	t.Helper()  	vk, sk, err := ed25519.GenerateKey(rand.Reader)  	if err != nil {  		t.Fatalf("must generate ed25519 keys: %v", err)  	} -	msg := types.Message{ +	msg := types.Statement{  		ShardHint: shardHint,  		Checksum:  checksum,  	} -	sig := ed25519.Sign(sk, msg.Marshal()) +	sig := ed25519.Sign(sk, msg.ToBinary())  	if !wantSig {  		sig[0] += 1  	}  	return bytes.NewBufferString(fmt.Sprintf( -		"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", -		types.ShardHint, types.Delim, shardHint, types.EOL, -		types.Checksum, types.Delim, checksum[:], types.EOL, -		types.Signature, types.Delim, sig, types.EOL, -		types.VerificationKey, types.Delim, vk, types.EOL, -		types.DomainHint, types.Delim, "example.com", types.EOL, +		"%s=%d\n"+"%s=%x\n"+"%s=%x\n"+"%s=%x\n"+"%s=%s\n", +		"shard_hint", shardHint, +		"checksum", checksum[:], +		"signature", sig, +		"verification_key", vk, +		"domain_hint", "example.com",  	))  } diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index 4dff31a..9f71aa3 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -3,16 +3,15 @@ package instance  import (  	"context"  	"crypto" -	"crypto/ed25519"  	"fmt"  	"net/http"  	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/requests" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db"  	"git.sigsum.org/sigsum-log-go/pkg/dns"  	"git.sigsum.org/sigsum-log-go/pkg/state" -	"git.sigsum.org/sigsum-log-go/pkg/trillian" -	"git.sigsum.org/sigsum-log-go/pkg/types" -	"github.com/golang/glog"  )  // Config is a collection of log parameters @@ -26,27 +25,18 @@ type Config struct {  	ShardStart uint64        // Shard interval start (num seconds since UNIX epoch)  	// Witnesses map trusted witness identifiers to public verification keys -	Witnesses map[[types.HashSize]byte][types.VerificationKeySize]byte +	Witnesses map[types.Hash]types.PublicKey  }  // Instance is an instance of the log's front-end  type Instance struct {  	Config                      // configuration parameters -	Client   trillian.Client    // provides access to the Trillian backend +	Client   db.Client          // provides access to the Trillian backend  	Signer   crypto.Signer      // provides access to Ed25519 private key  	Stateman state.StateManager // coordinates access to (co)signed tree heads  	DNS      dns.Verifier       // checks if domain name knows a public key  } -// Handler implements the http.Handler interface, and contains a reference -// to a sigsum server instance as well as a function that uses it. -type Handler struct { -	Instance *Instance -	Endpoint types.Endpoint -	Method   string -	Handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error) -} -  // Handlers returns a list of sigsum handlers  func (i *Instance) Handlers() []Handler {  	return []Handler{ @@ -62,51 +52,13 @@ func (i *Instance) Handlers() []Handler {  	}  } -// Path returns a path that should be configured for this handler -func (h Handler) Path() string { -	if len(h.Instance.Prefix) == 0 { -		return h.Endpoint.Path("", "sigsum", "v0") -	} -	return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0") -} - -// ServeHTTP is part of the http.Handler interface -func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { -	// export prometheus metrics -	var now time.Time = time.Now() -	var statusCode int -	defer func() { -		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) -		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode)) -	}() -	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint)) - -	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline)) -	defer cancel() - -	if r.Method != a.Method { -		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method) -		http.Error(w, "", http.StatusMethodNotAllowed) -		return -	} - -	statusCode, err := a.Handler(ctx, a.Instance, w, r) -	if err != nil { -		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err) -		http.Error(w, fmt.Sprintf("%s%s%s%s", "Error", types.Delim, err.Error(), types.EOL), statusCode) -	} -} - -func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*types.LeafRequest, error) { -	var req types.LeafRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*requests.Leaf, error) { +	var req requests.Leaf +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	} -	vk := ed25519.PublicKey(req.VerificationKey[:]) -	msg := req.Message.Marshal() -	sig := req.Signature[:] -	if !ed25519.Verify(vk, msg, sig) { +	if !req.Statement.Verify(&req.VerificationKey, &req.Signature) {  		return nil, fmt.Errorf("invalid signature")  	}  	shardEnd := uint64(time.Now().Unix()) @@ -116,27 +68,27 @@ func (i *Instance) leafRequestFromHTTP(ctx context.Context, r *http.Request) (*t  	if req.ShardHint > shardEnd {  		return nil, fmt.Errorf("invalid shard hint: %d not in [%d, %d]", req.ShardHint, i.ShardStart, shardEnd)  	} -	if err := i.DNS.Verify(ctx, req.DomainHint, req.VerificationKey); err != nil { +	if err := i.DNS.Verify(ctx, req.DomainHint, &req.VerificationKey); err != nil {  		return nil, fmt.Errorf("invalid domain hint: %v", err)  	}  	return &req, nil  } -func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*types.CosignatureRequest, error) { -	var req types.CosignatureRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) cosignatureRequestFromHTTP(r *http.Request) (*requests.Cosignature, error) { +	var req requests.Cosignature +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	} -	if _, ok := i.Witnesses[*req.KeyHash]; !ok { +	if _, ok := i.Witnesses[req.KeyHash]; !ok {  		return nil, fmt.Errorf("Unknown witness: %x", req.KeyHash)  	}  	return &req, nil  } -func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.ConsistencyProofRequest, error) { -	var req types.ConsistencyProofRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*requests.ConsistencyProof, error) { +	var req requests.ConsistencyProof +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.OldSize < 1 {  		return nil, fmt.Errorf("OldSize(%d) must be larger than zero", req.OldSize) @@ -147,10 +99,10 @@ func (i *Instance) consistencyProofRequestFromHTTP(r *http.Request) (*types.Cons  	return &req, nil  } -func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.InclusionProofRequest, error) { -	var req types.InclusionProofRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*requests.InclusionProof, error) { +	var req requests.InclusionProof +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.TreeSize < 2 {  		// TreeSize:0 => not possible to prove inclusion of anything @@ -160,10 +112,10 @@ func (i *Instance) inclusionProofRequestFromHTTP(r *http.Request) (*types.Inclus  	return &req, nil  } -func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*types.LeavesRequest, error) { -	var req types.LeavesRequest -	if err := req.UnmarshalASCII(r.Body); err != nil { -		return nil, fmt.Errorf("UnmarshalASCII: %v", err) +func (i *Instance) leavesRequestFromHTTP(r *http.Request) (*requests.Leaves, error) { +	var req requests.Leaves +	if err := req.FromASCII(r.Body); err != nil { +		return nil, fmt.Errorf("FromASCII: %v", err)  	}  	if req.StartSize > req.EndSize { diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go deleted file mode 100644 index 6ca7baf..0000000 --- a/pkg/instance/instance_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package instance - -import ( -	"net/http" -	"net/http/httptest" -	"testing" - -	"git.sigsum.org/sigsum-log-go/pkg/types" -) - -// TestHandlers check that the expected handlers are configured -func TestHandlers(t *testing.T) { -	endpoints := map[types.Endpoint]bool{ -		types.EndpointAddLeaf:             false, -		types.EndpointAddCosignature:      false, -		types.EndpointGetTreeHeadLatest:   false, -		types.EndpointGetTreeHeadToSign:   false, -		types.EndpointGetTreeHeadCosigned: false, -		types.EndpointGetConsistencyProof: false, -		types.EndpointGetInclusionProof:   false, -		types.EndpointGetLeaves:           false, -		types.Endpoint("get-checkpoint"):  false, -	} -	i := &Instance{ -		Config: testConfig, -	} -	for _, handler := range i.Handlers() { -		if _, ok := endpoints[handler.Endpoint]; !ok { -			t.Errorf("got unexpected endpoint: %s", handler.Endpoint) -		} -		endpoints[handler.Endpoint] = true -	} -	for endpoint, ok := range endpoints { -		if !ok { -			t.Errorf("endpoint %s is not configured", endpoint) -		} -	} -} - -// TestServeHTTP checks that invalid HTTP methods are rejected -func TestServeHTTP(t *testing.T) { -	i := &Instance{ -		Config: testConfig, -	} -	for _, handler := range i.Handlers() { -		// Prepare invalid HTTP request -		method := http.MethodPost -		if method == handler.Method { -			method = http.MethodGet -		} -		url := handler.Endpoint.Path("http://example.com", i.Prefix) -		req, err := http.NewRequest(method, url, nil) -		if err != nil { -			t.Fatalf("must create HTTP request: %v", err) -		} -		w := httptest.NewRecorder() - -		// Check that it is rejected -		handler.ServeHTTP(w, req) -		if got, want := w.Code, http.StatusMethodNotAllowed; got != want { -			t.Errorf("got HTTP code %v but wanted %v for endpoint %q", got, want, handler.Endpoint) -		} -	} -} - -// TestPath checks that Path works for an endpoint (add-leaf) -func TestPath(t *testing.T) { -	for _, table := range []struct { -		description string -		prefix      string -		want        string -	}{ -		{ -			description: "no prefix", -			want:        "/sigsum/v0/add-leaf", -		}, -		{ -			description: "a prefix", -			prefix:      "test-prefix", -			want:        "/test-prefix/sigsum/v0/add-leaf", -		}, -	} { -		instance := &Instance{ -			Config: Config{ -				Prefix: table.prefix, -			}, -		} -		handler := Handler{ -			Instance: instance, -			Handler:  addLeaf, -			Endpoint: types.EndpointAddLeaf, -			Method:   http.MethodPost, -		} -		if got, want := handler.Path(), table.want; got != want { -			t.Errorf("got path %v but wanted %v", got, want) -		} -	} -} diff --git a/pkg/mocks/crypto.go b/pkg/state/mocks/signer.go index 87c883a..7c699dd 100644 --- a/pkg/mocks/crypto.go +++ b/pkg/state/mocks/signer.go @@ -9,8 +9,8 @@ import (  // TestSign implements the signer interface.  It can be used to mock an Ed25519  // signer that always return the same public key, signature, and error.  type TestSigner struct { -	PublicKey *[ed25519.PublicKeySize]byte -	Signature *[ed25519.SignatureSize]byte +	PublicKey [ed25519.PublicKeySize]byte +	Signature [ed25519.SignatureSize]byte  	Error     error  } diff --git a/pkg/mocks/sigsum_state_manager.go b/pkg/state/mocks/state_manager.go index 3b74ac4..009e20f 100644 --- a/pkg/mocks/sigsum_state_manager.go +++ b/pkg/state/mocks/state_manager.go @@ -8,8 +8,8 @@ import (  	context "context"  	reflect "reflect" +	types "git.sigsum.org/sigsum-lib-go/pkg/types"  	gomock "github.com/golang/mock/gomock" -	types "git.sigsum.org/sigsum-log-go/pkg/types"  )  // MockStateManager is a mock of StateManager interface. @@ -36,7 +36,7 @@ func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder {  }  // AddCosignature mocks base method. -func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *[32]byte, arg2 *[64]byte) error { +func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *types.PublicKey, arg2 *types.Signature) error {  	m.ctrl.T.Helper()  	ret := m.ctrl.Call(m, "AddCosignature", arg0, arg1, arg2)  	ret0, _ := ret[0].(error) diff --git a/pkg/state/single.go b/pkg/state/single.go new file mode 100644 index 0000000..d02b86f --- /dev/null +++ b/pkg/state/single.go @@ -0,0 +1,156 @@ +package state + +import ( +	"context" +	"crypto" +	"crypto/ed25519" +	"fmt" +	"reflect" +	"sync" +	"time" + +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	"git.sigsum.org/sigsum-log-go/pkg/db" +	"github.com/golang/glog" +	"github.com/google/certificate-transparency-go/schedule" +) + +// StateManagerSingle implements a single-instance StateManager.  In other +// words, there's no other state that needs to be synced on any remote machine. +type StateManagerSingle struct { +	client   db.Client +	signer   crypto.Signer +	interval time.Duration +	deadline time.Duration +	sync.RWMutex + +	// cosigned is the current cosigned tree head that is being served +	cosigned types.CosignedTreeHead + +	// toSign is the current tree head that is being cosigned by witnesses +	toSign types.SignedTreeHead + +	// cosignatures keep track of all cosignatures for the toSign tree head +	cosignatures map[types.Hash]*types.Signature +} + +func NewStateManagerSingle(client db.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { +	sm := &StateManagerSingle{ +		client:   client, +		signer:   signer, +		interval: interval, +		deadline: deadline, +	} +	ctx, cancel := context.WithTimeout(context.Background(), sm.deadline) +	defer cancel() + +	sth, err := sm.Latest(ctx) +	if err != nil { +		return nil, fmt.Errorf("Latest: %v", err) +	} +	sm.toSign = *sth +	sm.cosignatures = make(map[types.Hash]*types.Signature) +	sm.cosigned = types.CosignedTreeHead{ +		SignedTreeHead: *sth, +		Cosignature:    make([]types.Signature, 0), +		KeyHash:        make([]types.Hash, 0), +	} +	return sm, nil +} + +func (sm *StateManagerSingle) Run(ctx context.Context) { +	schedule.Every(ctx, sm.interval, func(ctx context.Context) { +		ictx, cancel := context.WithTimeout(ctx, sm.deadline) +		defer cancel() + +		nextSTH, err := sm.Latest(ictx) +		if err != nil { +			glog.Warningf("rotate failed: Latest: %v", err) +			return +		} + +		sm.Lock() +		defer sm.Unlock() +		sm.rotate(nextSTH) +	}) +} + +func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { +	th, err := sm.client.GetTreeHead(ctx) +	if err != nil { +		return nil, fmt.Errorf("LatestTreeHead: %v", err) +	} + +	namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey)) +	return th.Sign(sm.signer, namespace) +} + +func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { +	sm.RLock() +	defer sm.RUnlock() +	return &sm.toSign, nil +} + +func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.CosignedTreeHead, error) { +	sm.RLock() +	defer sm.RUnlock() +	if len(sm.cosigned.Cosignature) == 0 { +		return nil, fmt.Errorf("no witness cosignatures available") +	} +	return &sm.cosigned, nil +} + +func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *types.PublicKey, sig *types.Signature) error { +	sm.Lock() +	defer sm.Unlock() + +	namespace := types.HashFn(sm.signer.Public().(ed25519.PublicKey)) +	msg := sm.toSign.TreeHead.ToBinary(namespace) +	if !ed25519.Verify(ed25519.PublicKey(vk[:]), msg, sig[:]) { +		return fmt.Errorf("invalid tree head signature") +	} + +	witness := types.HashFn(vk[:]) +	if _, ok := sm.cosignatures[*witness]; ok { +		return fmt.Errorf("signature-signer pair is a duplicate") // TODO: maybe not an error +	} +	sm.cosignatures[*witness] = sig + +	glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) +	return nil +} + +// rotate rotates the log's cosigned and stable STH.  The caller must aquire the +// source's read-write lock if there are concurrent reads and/or writes. +func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { +	if reflect.DeepEqual(sm.cosigned.SignedTreeHead, sm.toSign) { +		// cosigned and toSign are the same.  So, we need to merge all +		// cosignatures that we already had with the new collected ones. +		for i := 0; i < len(sm.cosigned.Cosignature); i++ { +			kh := sm.cosigned.KeyHash[i] +			sig := sm.cosigned.Cosignature[i] + +			if _, ok := sm.cosignatures[kh]; !ok { +				sm.cosignatures[kh] = &sig +			} +		} +		glog.V(3).Infof("cosigned tree head repeated, merged signatures") +	} +	var cosignatures []types.Signature +	var keyHashes []types.Hash + +	for keyHash, cosignature := range sm.cosignatures { +		cosignatures = append(cosignatures, *cosignature) +		keyHashes = append(keyHashes, keyHash) +	} + +	// Update cosigned tree head +	sm.cosigned.SignedTreeHead = sm.toSign +	sm.cosigned.Cosignature = cosignatures +	sm.cosigned.KeyHash = keyHashes + +	// Update to-sign tree head +	sm.toSign = *next +	sm.cosignatures = make(map[types.Hash]*types.Signature, 0) // TODO: on repeat we might want to not zero this +	glog.V(3).Infof("rotated tree heads") +} diff --git a/pkg/state/state_manager_test.go b/pkg/state/single_test.go index f11ba28..b315b3e 100644 --- a/pkg/state/state_manager_test.go +++ b/pkg/state/single_test.go @@ -11,36 +11,34 @@ import (  	"testing"  	"time" +	"git.sigsum.org/sigsum-lib-go/pkg/types" +	mocksTrillian "git.sigsum.org/sigsum-log-go/pkg/db/mocks" +	mocksSigner "git.sigsum.org/sigsum-log-go/pkg/state/mocks"  	"github.com/golang/mock/gomock" -	"git.sigsum.org/sigsum-log-go/pkg/mocks" -	"git.sigsum.org/sigsum-log-go/pkg/types"  )  var ( -	testSig = &[types.SignatureSize]byte{} -	testPub = &[types.VerificationKeySize]byte{} -	testTH  = &types.TreeHead{ +	testTH = types.TreeHead{  		Timestamp: 0,  		TreeSize:  0, -		RootHash:  types.Hash(nil), -		KeyHash:   types.Hash(testPub[:]), +		RootHash:  types.Hash{},  	} -	testSigIdent = &types.SigIdent{ -		Signature: testSig, -		KeyHash:   types.Hash(testPub[:]), +	testSTH = types.SignedTreeHead{ +		TreeHead:  testTH, +		Signature: types.Signature{},  	} -	testSTH = &types.SignedTreeHead{ -		TreeHead:  *testTH, -		Signature: testSig, -	} -	testCTH = &types.CosignedTreeHead{ -		SignedTreeHead: *testSTH, -		SigIdent: []*types.SigIdent{ -			testSigIdent, +	testCTH = types.CosignedTreeHead{ +		SignedTreeHead: testSTH, +		Cosignature: []types.Signature{ +			types.Signature{}, +		}, +		KeyHash: []types.Hash{ +			types.Hash{},  		},  	} -	testSignerOK  = &mocks.TestSigner{testPub, testSig, nil} -	testSignerErr = &mocks.TestSigner{testPub, testSig, fmt.Errorf("something went wrong")} + +	testSignerOK  = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, nil} +	testSignerErr = &mocksSigner.TestSigner{types.PublicKey{}, types.Signature{}, fmt.Errorf("something went wrong")}  )  func TestNewStateManagerSingle(t *testing.T) { @@ -61,15 +59,15 @@ func TestNewStateManagerSingle(t *testing.T) {  		{  			description: "valid",  			signer:      testSignerOK, -			rsp:         testTH, -			wantSth:     testSTH, +			rsp:         &testTH, +			wantSth:     &testSTH,  		},  	} {  		// Run deferred functions at the end of each iteration  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksTrillian.NewMockClient(ctrl)  			client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)  			sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0)) @@ -82,11 +80,11 @@ func TestNewStateManagerSingle(t *testing.T) {  			if got, want := &sm.cosigned.SignedTreeHead, table.wantSth; !reflect.DeepEqual(got, want) {  				t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  			} -			if got, want := &sm.tosign, table.wantSth; !reflect.DeepEqual(got, want) { -				t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) +			if got, want := &sm.toSign, table.wantSth; !reflect.DeepEqual(got, want) { +				t.Errorf("got toSign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  			}  			// we only have log signature on startup -			if got, want := len(sm.cosignature), 0; got != want { +			if got, want := len(sm.cosignatures), 0; got != want {  				t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, table.description)  			}  		}() @@ -110,22 +108,22 @@ func TestLatest(t *testing.T) {  		},  		{  			description: "invalid: signature failure", -			rsp:         testTH, +			rsp:         &testTH,  			signer:      testSignerErr,  			wantErr:     true,  		},  		{  			description: "valid",  			signer:      testSignerOK, -			rsp:         testTH, -			wantSth:     testSTH, +			rsp:         &testTH, +			wantSth:     &testSTH,  		},  	} {  		// Run deferred functions at the end of each iteration  		func() {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			client := mocks.NewMockClient(ctrl) +			client := mocksTrillian.NewMockClient(ctrl)  			client.EXPECT().GetTreeHead(gomock.Any()).Return(table.rsp, table.err)  			sm := StateManagerSingle{  				client: client, @@ -149,14 +147,14 @@ func TestLatest(t *testing.T) {  func TestToSign(t *testing.T) {  	description := "valid"  	sm := StateManagerSingle{ -		tosign: *testSTH, +		toSign: testSTH,  	}  	sth, err := sm.ToSign(context.Background())  	if err != nil {  		t.Errorf("ToSign should not fail with error: %v", err)  		return  	} -	if got, want := sth, testSTH; !reflect.DeepEqual(got, want) { +	if got, want := sth, &testSTH; !reflect.DeepEqual(got, want) {  		t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description)  	}  } @@ -164,18 +162,19 @@ func TestToSign(t *testing.T) {  func TestCosigned(t *testing.T) {  	description := "valid"  	sm := StateManagerSingle{ -		cosigned: *testCTH, +		cosigned: testCTH,  	}  	cth, err := sm.Cosigned(context.Background())  	if err != nil {  		t.Errorf("Cosigned should not fail with error: %v", err)  		return  	} -	if got, want := cth, testCTH; !reflect.DeepEqual(got, want) { +	if got, want := cth, &testCTH; !reflect.DeepEqual(got, want) {  		t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, description)  	} -	sm.cosigned.SigIdent = make([]*types.SigIdent, 0) +	sm.cosigned.Cosignature = make([]types.Signature, 0) +	sm.cosigned.KeyHash = make([]types.Hash, 0)  	cth, err = sm.Cosigned(context.Background())  	if err == nil {  		t.Errorf("Cosigned should fail without witness cosignatures") @@ -188,57 +187,55 @@ func TestAddCosignature(t *testing.T) {  	if err != nil {  		t.Fatalf("GenerateKey: %v", err)  	} -	if bytes.Equal(vk[:], testPub[:]) { +	if bytes.Equal(vk[:], new(types.PublicKey)[:]) {  		t.Fatalf("Sampled same key as testPub, aborting...")  	} -	var vkArray [types.VerificationKeySize]byte +	var vkArray types.PublicKey  	copy(vkArray[:], vk[:])  	for _, table := range []struct {  		description string  		signer      crypto.Signer -		vk          *[types.VerificationKeySize]byte -		th          *types.TreeHead +		vk          types.PublicKey +		th          types.TreeHead  		wantErr     bool  	}{  		{  			description: "invalid: signature error",  			signer:      sk, -			vk:          testPub, // wrong key for message +			vk:          types.PublicKey{}, // wrong key for message  			th:          testTH,  			wantErr:     true,  		},  		{  			description: "valid",  			signer:      sk, -			vk:          &vkArray, +			vk:          vkArray,  			th:          testTH,  		},  	} { -		sth, _ := table.th.Sign(testSignerOK) +		kh := types.HashFn(testSignerOK.Public().(ed25519.PublicKey)) +		sth := mustSign(t, testSignerOK, &table.th, kh)  		cth := &types.CosignedTreeHead{  			SignedTreeHead: *sth, -			SigIdent:       []*types.SigIdent{}, +			Cosignature:    make([]types.Signature, 0), +			KeyHash:        make([]types.Hash, 0),  		}  		sm := &StateManagerSingle{ -			signer:      testSignerOK, -			cosigned:    *cth, -			tosign:      *sth, -			cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +			signer:       testSignerOK, +			cosigned:     *cth, +			toSign:       *sth, +			cosignatures: make(map[types.Hash]*types.Signature, 0),  		}  		// Prepare witness signature -		sth, err := table.th.Sign(table.signer) -		if err != nil { -			t.Fatalf("Sign: %v", err) -		} -		si := &types.SigIdent{ -			KeyHash:   types.Hash(table.signer.Public().(ed25519.PublicKey)[:]), -			Signature: sth.Signature, -		} +		var vk types.PublicKey +		copy(vk[:], table.vk[:]) //table.signer.Public().(ed25519.PublicKey)) +		sth = mustSign(t, table.signer, &table.th, kh) +		kh = types.HashFn(vk[:])  		// Add witness signature -		err = sm.AddCosignature(context.Background(), table.vk, si.Signature) +		err = sm.AddCosignature(context.Background(), &vk, &sth.Signature)  		if got, want := err != nil, table.wantErr; got != want {  			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err)  		} @@ -247,48 +244,47 @@ func TestAddCosignature(t *testing.T) {  		}  		// We should have one witness signature -		if got, want := len(sm.cosignature), 1; got != want { +		if got, want := len(sm.cosignatures), 1; got != want {  			t.Errorf("got %d cosignatures but wanted %v in test %q", got, want, table.description)  			continue  		}  		// check that witness signature is there -		sigident, ok := sm.cosignature[*si.KeyHash] +		sig, ok := sm.cosignatures[*kh]  		if !ok {  			t.Errorf("witness signature is missing")  			continue  		} -		if got, want := si, sigident; !reflect.DeepEqual(got, want) { +		if got, want := sig[:], sth.Signature[:]; !bytes.Equal(got, want) {  			t.Errorf("got witness sigident\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  			continue  		}  		// Adding a duplicate signature should give an error -		if err := sm.AddCosignature(context.Background(), table.vk, si.Signature); err == nil { +		if err := sm.AddCosignature(context.Background(), &vk, &sth.Signature); err == nil {  			t.Errorf("duplicate witness signature accepted as valid")  		}  	}  }  func TestRotate(t *testing.T) { -	log := testSigIdent -	wit1 := &types.SigIdent{ -		Signature: testSig, -		KeyHash:   types.Hash([]byte("wit1 key")), -	} -	wit2 := &types.SigIdent{ -		Signature: testSig, -		KeyHash:   types.Hash([]byte("wit2 key")), -	} -	th0 := testTH +	logSig := types.Signature{} +	wit1Sig := types.Signature{} +	wit2Sig := types.Signature{} + +	//logKH := &types.Hash{} +	wit1KH := types.HashFn([]byte("wit1 key")) +	wit2KH := types.HashFn([]byte("wit2 key")) + +	th0 := &testTH  	th1 := &types.TreeHead{  		Timestamp: 1,  		TreeSize:  1, -		RootHash:  types.Hash([]byte("1")), +		RootHash:  *types.HashFn([]byte("1")),  	}  	th2 := &types.TreeHead{  		Timestamp: 2,  		TreeSize:  2, -		RootHash:  types.Hash([]byte("2")), +		RootHash:  *types.HashFn([]byte("2")),  	}  	for _, table := range []struct { @@ -297,77 +293,81 @@ func TestRotate(t *testing.T) {  		next          *types.SignedTreeHead  	}{  		{ -			description: "tosign tree head repated, but got one new witnes signature", +			description: "toSign tree head repated, but got one new witnes signature",  			before: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1}, +					Cosignature: []types.Signature{wit1Sig}, +					KeyHash:     []types.Hash{*wit1KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th0, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{ -					*wit2.KeyHash: wit2, // the new witness signature +				cosignatures: map[types.Hash]*types.Signature{ +					*wit2KH: &wit2Sig, // the new witness signature  				},  			},  			next: &types.SignedTreeHead{  				TreeHead:  *th1, -				Signature: log.Signature, +				Signature: logSig,  			},  			after: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1, wit2}, +					Cosignature: []types.Signature{wit1Sig, wit2Sig}, +					KeyHash:     []types.Hash{*wit1KH, *wit2KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th1, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +				cosignatures: map[types.Hash]*types.Signature{},  			},  		},  		{ -			description: "tosign tree head did not repeat, it got one witness signature", +			description: "toSign tree head did not repeat, it got one witness signature",  			before: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th0, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit1}, +					Cosignature: []types.Signature{wit1Sig}, +					KeyHash:     []types.Hash{*wit1KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th1, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{ -					*log.KeyHash: wit2, +				cosignatures: map[types.Hash]*types.Signature{ +					*wit2KH: &wit2Sig,  				},  			},  			next: &types.SignedTreeHead{  				TreeHead:  *th2, -				Signature: log.Signature, +				Signature: logSig,  			},  			after: &StateManagerSingle{  				cosigned: types.CosignedTreeHead{  					SignedTreeHead: types.SignedTreeHead{  						TreeHead:  *th1, -						Signature: log.Signature, +						Signature: logSig,  					}, -					SigIdent: []*types.SigIdent{wit2}, +					Cosignature: []types.Signature{wit2Sig}, +					KeyHash:     []types.Hash{*wit2KH},  				}, -				tosign: types.SignedTreeHead{ +				toSign: types.SignedTreeHead{  					TreeHead:  *th2, -					Signature: log.Signature, +					Signature: logSig,  				}, -				cosignature: map[[types.HashSize]byte]*types.SigIdent{}, +				cosignatures: map[types.Hash]*types.Signature{},  			},  		},  	} { @@ -375,31 +375,44 @@ func TestRotate(t *testing.T) {  		if got, want := table.before.cosigned.SignedTreeHead, table.after.cosigned.SignedTreeHead; !reflect.DeepEqual(got, want) {  			t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  		} -		checkWitnessList(t, table.description, table.before.cosigned.SigIdent, table.after.cosigned.SigIdent) -		if got, want := table.before.tosign, table.after.tosign; !reflect.DeepEqual(got, want) { -			t.Errorf("got tosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) +		checkWitnessList(t, table.description, table.before.cosigned, table.after.cosigned) +		if got, want := table.before.toSign, table.after.toSign; !reflect.DeepEqual(got, want) { +			t.Errorf("got toSign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  		} -		if got, want := table.before.cosignature, table.after.cosignature; !reflect.DeepEqual(got, want) { -			t.Errorf("got cosignature map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) +		if got, want := table.before.cosignatures, table.after.cosignatures; !reflect.DeepEqual(got, want) { +			t.Errorf("got cosignatures map\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)  		}  	}  } -func checkWitnessList(t *testing.T, description string, got, want []*types.SigIdent) { +func checkWitnessList(t *testing.T, description string, got, want types.CosignedTreeHead) {  	t.Helper() -	for _, si := range got { +	if got, want := len(got.Cosignature), len(want.Cosignature); got != want { +		t.Errorf("got %d cosignatures but wanted %d in test %q", got, want, description) +		return +	} +	if got, want := len(got.KeyHash), len(want.KeyHash); got != want { +		t.Errorf("got %d key hashes but wanted %d in test %q", got, want, description) +		return +	} +	for i := 0; i < len(got.Cosignature); i++ {  		found := false -		for _, sj := range want { -			if reflect.DeepEqual(si, sj) { +		for j := 0; j < len(want.Cosignature); j++ { +			if bytes.Equal(got.Cosignature[i][:], want.Cosignature[j][:]) && bytes.Equal(got.KeyHash[i][:], want.KeyHash[j][:]) {  				found = true  				break  			}  		}  		if !found { -			t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, si.KeyHash[:]) +			t.Errorf("got unexpected signature-signer pair with key hash in test %q: %x", description, got.KeyHash[i][:])  		}  	} -	if len(got) != len(want) { -		t.Errorf("got %d signature-signer pairs but wanted %d in test %q", len(got), len(want), description) +} + +func mustSign(t *testing.T, s crypto.Signer, th *types.TreeHead, kh *types.Hash) *types.SignedTreeHead { +	sth, err := th.Sign(s, kh) +	if err != nil { +		t.Fatal(err)  	} +	return sth  } diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go index 84431be..5aa7609 100644 --- a/pkg/state/state_manager.go +++ b/pkg/state/state_manager.go @@ -2,157 +2,15 @@ package state  import (  	"context" -	"crypto" -	"crypto/ed25519" -	"fmt" -	"reflect" -	"sync" -	"time" -	"github.com/golang/glog" -	"github.com/google/certificate-transparency-go/schedule" -	"git.sigsum.org/sigsum-log-go/pkg/trillian" -	"git.sigsum.org/sigsum-log-go/pkg/types" +	"git.sigsum.org/sigsum-lib-go/pkg/types"  ) -// StateManager coordinates access to the log's tree heads and (co)signatures +// StateManager coordinates access to a log's tree heads and (co)signatures  type StateManager interface {  	Latest(context.Context) (*types.SignedTreeHead, error)  	ToSign(context.Context) (*types.SignedTreeHead, error)  	Cosigned(context.Context) (*types.CosignedTreeHead, error) -	AddCosignature(context.Context, *[types.VerificationKeySize]byte, *[types.SignatureSize]byte) error +	AddCosignature(context.Context, *types.PublicKey, *types.Signature) error  	Run(context.Context)  } - -// StateManagerSingle implements the StateManager interface.  It is assumed that -// the log server is running on a single-instance machine.  So, no coordination. -type StateManagerSingle struct { -	client   trillian.Client -	signer   crypto.Signer -	interval time.Duration -	deadline time.Duration -	sync.RWMutex - -	// cosigned is the current cosigned tree head that is being served -	cosigned types.CosignedTreeHead - -	// tosign is the current tree head that is being cosigned by witnesses -	tosign types.SignedTreeHead - -	// cosignature keeps track of all cosignatures for the tosign tree head -	cosignature map[[types.HashSize]byte]*types.SigIdent -} - -func NewStateManagerSingle(client trillian.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) { -	sm := &StateManagerSingle{ -		client:   client, -		signer:   signer, -		interval: interval, -		deadline: deadline, -	} - -	ctx, _ := context.WithTimeout(context.Background(), sm.deadline) -	sth, err := sm.Latest(ctx) -	if err != nil { -		return nil, fmt.Errorf("Latest: %v", err) -	} - -	sm.cosigned = types.CosignedTreeHead{ -		SignedTreeHead: *sth, -		SigIdent:       []*types.SigIdent{}, -	} -	sm.tosign = *sth -	sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{} -	return sm, nil -} - -func (sm *StateManagerSingle) Run(ctx context.Context) { -	schedule.Every(ctx, sm.interval, func(ctx context.Context) { -		ictx, _ := context.WithTimeout(ctx, sm.deadline) -		nextSTH, err := sm.Latest(ictx) -		if err != nil { -			glog.Warningf("rotate failed: Latest: %v", err) -			return -		} - -		sm.Lock() -		defer sm.Unlock() -		sm.rotate(nextSTH) -	}) -} - -func (sm *StateManagerSingle) Latest(ctx context.Context) (*types.SignedTreeHead, error) { -	th, err := sm.client.GetTreeHead(ctx) -	if err != nil { -		return nil, fmt.Errorf("LatestTreeHead: %v", err) -	} -	th.KeyHash = types.Hash(sm.signer.Public().(ed25519.PublicKey)[:]) -	sth, err := th.Sign(sm.signer) -	if err != nil { -		return nil, fmt.Errorf("sign: %v", err) -	} -	return sth, nil -} - -func (sm *StateManagerSingle) ToSign(_ context.Context) (*types.SignedTreeHead, error) { -	sm.RLock() -	defer sm.RUnlock() -	return &sm.tosign, nil -} - -func (sm *StateManagerSingle) Cosigned(_ context.Context) (*types.CosignedTreeHead, error) { -	sm.RLock() -	defer sm.RUnlock() -	if len(sm.cosigned.SigIdent) == 0 { -		return nil, fmt.Errorf("no witness cosignatures available") -	} -	return &sm.cosigned, nil -} - -func (sm *StateManagerSingle) AddCosignature(_ context.Context, vk *[types.VerificationKeySize]byte, sig *[types.SignatureSize]byte) error { -	sm.Lock() -	defer sm.Unlock() - -	if err := sm.tosign.TreeHead.Verify(vk, sig); err != nil { -		return fmt.Errorf("Verify: %v", err) -	} -	witness := types.Hash(vk[:]) -	if _, ok := sm.cosignature[*witness]; ok { -		return fmt.Errorf("signature-signer pair is a duplicate") -	} -	sm.cosignature[*witness] = &types.SigIdent{ -		Signature: sig, -		KeyHash:   witness, -	} - -	glog.V(3).Infof("accepted new cosignature from witness: %x", *witness) -	return nil -} - -// rotate rotates the log's cosigned and stable STH.  The caller must aquire the -// source's read-write lock if there are concurrent reads and/or writes. -func (sm *StateManagerSingle) rotate(next *types.SignedTreeHead) { -	if reflect.DeepEqual(sm.cosigned.SignedTreeHead, sm.tosign) { -		// cosigned and tosign are the same.  So, we need to merge all -		// cosignatures that we already had with the new collected ones. -		for _, sigident := range sm.cosigned.SigIdent { -			if _, ok := sm.cosignature[*sigident.KeyHash]; !ok { -				sm.cosignature[*sigident.KeyHash] = sigident -			} -		} -		glog.V(3).Infof("cosigned tree head repeated, merged signatures") -	} -	var cosignatures []*types.SigIdent -	for _, sigident := range sm.cosignature { -		cosignatures = append(cosignatures, sigident) -	} - -	// Update cosigned tree head -	sm.cosigned.SignedTreeHead = sm.tosign -	sm.cosigned.SigIdent = cosignatures - -	// Update to-sign tree head -	sm.tosign = *next -	sm.cosignature = map[[types.HashSize]byte]*types.SigIdent{} // TODO: on repeat we might want to not zero this -	glog.V(3).Infof("rotated tree heads") -} diff --git a/pkg/trillian/util.go b/pkg/trillian/util.go deleted file mode 100644 index fbd0aea..0000000 --- a/pkg/trillian/util.go +++ /dev/null @@ -1,33 +0,0 @@ -package trillian - -import ( -	"fmt" - -	trillian "github.com/google/trillian/types" -	sigsum "git.sigsum.org/sigsum-log-go/pkg/types" -) - -func treeHeadFromLogRoot(lr *trillian.LogRootV1) *sigsum.TreeHead { -	var hash [sigsum.HashSize]byte -	th := sigsum.TreeHead{ -		Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), -		TreeSize:  uint64(lr.TreeSize), -		RootHash:  &hash, -	} -	copy(th.RootHash[:], lr.RootHash) -	return &th -} - -func nodePathFromHashes(hashes [][]byte) ([]*[sigsum.HashSize]byte, error) { -	var path []*[sigsum.HashSize]byte -	for _, hash := range hashes { -		if len(hash) != sigsum.HashSize { -			return nil, fmt.Errorf("unexpected hash length: %v", len(hash)) -		} - -		var h [sigsum.HashSize]byte -		copy(h[:], hash) -		path = append(path, &h) -	} -	return path, nil -} diff --git a/pkg/types/ascii.go b/pkg/types/ascii.go deleted file mode 100644 index 72abfcb..0000000 --- a/pkg/types/ascii.go +++ /dev/null @@ -1,399 +0,0 @@ -package types - -import ( -	"bytes" -	"encoding/hex" -	"fmt" -	"io" -	"io/ioutil" -	"strconv" -) - -const ( -	// Delim is a key-value separator -	Delim = "=" - -	// EOL is a line sepator -	EOL = "\n" - -	// NumField* is the number of unique keys in an incoming ASCII message -	NumFieldLeaf                    = 4 -	NumFieldSignedTreeHead          = 4 -	NumFieldConsistencyProof        = 3 -	NumFieldInclusionProof          = 3 -	NumFieldLeavesRequest           = 2 -	NumFieldInclusionProofRequest   = 2 -	NumFieldConsistencyProofRequest = 2 -	NumFieldLeafRequest             = 5 -	NumFieldCosignatureRequest      = 2 - -	// New leaf keys -	ShardHint       = "shard_hint" -	Checksum        = "checksum" -	Signature       = "signature" -	VerificationKey = "verification_key" -	DomainHint      = "domain_hint" - -	// Inclusion proof keys -	LeafHash      = "leaf_hash" -	LeafIndex     = "leaf_index" -	InclusionPath = "inclusion_path" - -	// Consistency proof keys -	NewSize         = "new_size" -	OldSize         = "old_size" -	ConsistencyPath = "consistency_path" - -	// Range of leaves keys -	StartSize = "start_size" -	EndSize   = "end_size" - -	// Tree head keys -	Timestamp = "timestamp" -	TreeSize  = "tree_size" -	RootHash  = "root_hash" - -	// Witness signature-identity keys -	KeyHash     = "key_hash" -	Cosignature = "cosignature" -) - -// MessageASCI is a wrapper that manages ASCII key-value pairs -type MessageASCII struct { -	m map[string][]string -} - -// NewMessageASCII unpacks an incoming ASCII message -func NewMessageASCII(r io.Reader, numFieldExpected int) (*MessageASCII, error) { -	buf, err := ioutil.ReadAll(r) -	if err != nil { -		return nil, fmt.Errorf("ReadAll: %v", err) -	} -	lines := bytes.Split(buf, []byte(EOL)) -	if len(lines) <= 1 { -		return nil, fmt.Errorf("Not enough lines: empty") -	} -	lines = lines[:len(lines)-1] // valid message => split gives empty last line - -	msg := MessageASCII{make(map[string][]string)} -	for _, line := range lines { -		split := bytes.Index(line, []byte(Delim)) -		if split == -1 { -			return nil, fmt.Errorf("invalid line: %v", string(line)) -		} - -		key := string(line[:split]) -		value := string(line[split+len(Delim):]) -		values, ok := msg.m[key] -		if !ok { -			values = nil -			msg.m[key] = values -		} -		msg.m[key] = append(values, value) -	} - -	if msg.NumField() != numFieldExpected { -		return nil, fmt.Errorf("Unexpected number of keys: %v", msg.NumField()) -	} -	return &msg, nil -} - -// NumField returns the number of unique keys -func (msg *MessageASCII) NumField() int { -	return len(msg.m) -} - -// GetStrings returns a list of strings -func (msg *MessageASCII) GetStrings(key string) []string { -	strs, ok := msg.m[key] -	if !ok { -		return nil -	} -	return strs -} - -// GetString unpacks a string -func (msg *MessageASCII) GetString(key string) (string, error) { -	strs := msg.GetStrings(key) -	if len(strs) != 1 { -		return "", fmt.Errorf("expected one string: %v", strs) -	} -	return strs[0], nil -} - -// GetUint64 unpacks an uint64 -func (msg *MessageASCII) GetUint64(key string) (uint64, error) { -	str, err := msg.GetString(key) -	if err != nil { -		return 0, fmt.Errorf("GetString: %v", err) -	} -	num, err := strconv.ParseUint(str, 10, 64) -	if err != nil { -		return 0, fmt.Errorf("ParseUint: %v", err) -	} -	return num, nil -} - -// GetHash unpacks a hash -func (msg *MessageASCII) GetHash(key string) (*[HashSize]byte, error) { -	str, err := msg.GetString(key) -	if err != nil { -		return nil, fmt.Errorf("GetString: %v", err) -	} - -	var hash [HashSize]byte -	if err := decodeHex(str, hash[:]); err != nil { -		return nil, fmt.Errorf("decodeHex: %v", err) -	} -	return &hash, nil -} - -// GetSignature unpacks a signature -func (msg *MessageASCII) GetSignature(key string) (*[SignatureSize]byte, error) { -	str, err := msg.GetString(key) -	if err != nil { -		return nil, fmt.Errorf("GetString: %v", err) -	} - -	var signature [SignatureSize]byte -	if err := decodeHex(str, signature[:]); err != nil { -		return nil, fmt.Errorf("decodeHex: %v", err) -	} -	return &signature, nil -} - -// GetVerificationKey unpacks a verification key -func (msg *MessageASCII) GetVerificationKey(key string) (*[VerificationKeySize]byte, error) { -	str, err := msg.GetString(key) -	if err != nil { -		return nil, fmt.Errorf("GetString: %v", err) -	} - -	var vk [VerificationKeySize]byte -	if err := decodeHex(str, vk[:]); err != nil { -		return nil, fmt.Errorf("decodeHex: %v", err) -	} -	return &vk, nil -} - -// decodeHex decodes a hex-encoded string into an already-sized byte slice -func decodeHex(str string, out []byte) error { -	buf, err := hex.DecodeString(str) -	if err != nil { -		return fmt.Errorf("DecodeString: %v", err) -	} -	if len(buf) != len(out) { -		return fmt.Errorf("invalid length: %v", len(buf)) -	} -	copy(out, buf) -	return nil -} - -/* - * - * MarshalASCII wrappers for types that the log server outputs - * - */ -func (l *Leaf) MarshalASCII(w io.Writer) error { -	if err := writeASCII(w, ShardHint, strconv.FormatUint(l.ShardHint, 10)); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, Checksum, hex.EncodeToString(l.Checksum[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, Signature, hex.EncodeToString(l.Signature[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, KeyHash, hex.EncodeToString(l.KeyHash[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	return nil -} - -func (sth *SignedTreeHead) MarshalASCII(w io.Writer) error { -	if err := writeASCII(w, Timestamp, strconv.FormatUint(sth.Timestamp, 10)); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, TreeSize, strconv.FormatUint(sth.TreeSize, 10)); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, RootHash, hex.EncodeToString(sth.RootHash[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, Signature, hex.EncodeToString(sth.Signature[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	return nil -} - -func (cth *CosignedTreeHead) MarshalASCII(w io.Writer) error { -	if err := cth.SignedTreeHead.MarshalASCII(w); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	for _, si := range cth.SigIdent { -		if err := si.MarshalASCII(w); err != nil { -			return fmt.Errorf("writeASCII: %v", err) -		} -	} -	return nil -} - -func (si *SigIdent) MarshalASCII(w io.Writer) error { -	if err := writeASCII(w, KeyHash, hex.EncodeToString(si.KeyHash[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	if err := writeASCII(w, Cosignature, hex.EncodeToString(si.Signature[:])); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	return nil -} - -func (p *ConsistencyProof) MarshalASCII(w io.Writer) error { -	for _, hash := range p.Path { -		if err := writeASCII(w, ConsistencyPath, hex.EncodeToString(hash[:])); err != nil { -			return fmt.Errorf("writeASCII: %v", err) -		} -	} -	return nil -} - -func (p *InclusionProof) MarshalASCII(w io.Writer) error { -	if err := writeASCII(w, LeafIndex, strconv.FormatUint(p.LeafIndex, 10)); err != nil { -		return fmt.Errorf("writeASCII: %v", err) -	} -	for _, hash := range p.Path { -		if err := writeASCII(w, InclusionPath, hex.EncodeToString(hash[:])); err != nil { -			return fmt.Errorf("writeASCII: %v", err) -		} -	} -	return nil -} - -func writeASCII(w io.Writer, key, value string) error { -	if _, err := fmt.Fprintf(w, "%s%s%s%s", key, Delim, value, EOL); err != nil { -		return fmt.Errorf("Fprintf: %v", err) -	} -	return nil -} - -/* - * - * Unmarshal ASCII wrappers that the log server and/or log clients receive. - * - */ -func (ll *LeafList) UnmarshalASCII(r io.Reader) error { -	return nil -} - -func (sth *SignedTreeHead) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldSignedTreeHead) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if sth.Timestamp, err = msg.GetUint64(Timestamp); err != nil { -		return fmt.Errorf("GetUint64(Timestamp): %v", err) -	} -	if sth.TreeSize, err = msg.GetUint64(TreeSize); err != nil { -		return fmt.Errorf("GetUint64(TreeSize): %v", err) -	} -	if sth.RootHash, err = msg.GetHash(RootHash); err != nil { -		return fmt.Errorf("GetHash(RootHash): %v", err) -	} -	if sth.Signature, err = msg.GetSignature(Signature); err != nil { -		return fmt.Errorf("GetHash(RootHash): %v", err) -	} -	return nil -} - -func (p *InclusionProof) UnmarshalASCII(r io.Reader) error { -	return nil -} - -func (p *ConsistencyProof) UnmarshalASCII(r io.Reader) error { -	return nil -} - -func (req *InclusionProofRequest) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldInclusionProofRequest) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if req.LeafHash, err = msg.GetHash(LeafHash); err != nil { -		return fmt.Errorf("GetHash(LeafHash): %v", err) -	} -	if req.TreeSize, err = msg.GetUint64(TreeSize); err != nil { -		return fmt.Errorf("GetUint64(TreeSize): %v", err) -	} -	return nil -} - -func (req *ConsistencyProofRequest) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldConsistencyProofRequest) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if req.NewSize, err = msg.GetUint64(NewSize); err != nil { -		return fmt.Errorf("GetUint64(NewSize): %v", err) -	} -	if req.OldSize, err = msg.GetUint64(OldSize); err != nil { -		return fmt.Errorf("GetUint64(OldSize): %v", err) -	} -	return nil -} - -func (req *LeavesRequest) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldLeavesRequest) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if req.StartSize, err = msg.GetUint64(StartSize); err != nil { -		return fmt.Errorf("GetUint64(StartSize): %v", err) -	} -	if req.EndSize, err = msg.GetUint64(EndSize); err != nil { -		return fmt.Errorf("GetUint64(EndSize): %v", err) -	} -	return nil -} - -func (req *LeafRequest) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldLeafRequest) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if req.ShardHint, err = msg.GetUint64(ShardHint); err != nil { -		return fmt.Errorf("GetUint64(ShardHint): %v", err) -	} -	if req.Checksum, err = msg.GetHash(Checksum); err != nil { -		return fmt.Errorf("GetHash(Checksum): %v", err) -	} -	if req.Signature, err = msg.GetSignature(Signature); err != nil { -		return fmt.Errorf("GetSignature: %v", err) -	} -	if req.VerificationKey, err = msg.GetVerificationKey(VerificationKey); err != nil { -		return fmt.Errorf("GetVerificationKey: %v", err) -	} -	if req.DomainHint, err = msg.GetString(DomainHint); err != nil { -		return fmt.Errorf("GetString(DomainHint): %v", err) -	} -	return nil -} - -func (req *CosignatureRequest) UnmarshalASCII(r io.Reader) error { -	msg, err := NewMessageASCII(r, NumFieldCosignatureRequest) -	if err != nil { -		return fmt.Errorf("NewMessageASCII: %v", err) -	} - -	if req.Signature, err = msg.GetSignature(Cosignature); err != nil { -		return fmt.Errorf("GetSignature: %v", err) -	} -	if req.KeyHash, err = msg.GetHash(KeyHash); err != nil { -		return fmt.Errorf("GetHash(KeyHash): %v", err) -	} -	return nil -} diff --git a/pkg/types/ascii_test.go b/pkg/types/ascii_test.go deleted file mode 100644 index fc3f486..0000000 --- a/pkg/types/ascii_test.go +++ /dev/null @@ -1,438 +0,0 @@ -package types - -import ( -	"bytes" -	"fmt" -	"io" -	"reflect" -	"testing" -) - -/* - * - * MessageASCII methods and helpers - * - */ -func TestNewMessageASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		input       io.Reader -		wantErr     bool -		wantMap     map[string][]string -	}{ -		{ -			description: "invalid: not enough lines", -			input:       bytes.NewBufferString(""), -			wantErr:     true, -		}, -		{ -			description: "invalid: lines must end with new line", -			input:       bytes.NewBufferString("k1=v1\nk2=v2"), -			wantErr:     true, -		}, -		{ -			description: "invalid: lines must not be empty", -			input:       bytes.NewBufferString("k1=v1\n\nk2=v2\n"), -			wantErr:     true, -		}, -		{ -			description: "invalid: wrong number of fields", -			input:       bytes.NewBufferString("k1=v1\n"), -			wantErr:     true, -		}, -		{ -			description: "valid", -			input:       bytes.NewBufferString("k1=v1\nk2=v2\nk2=v3=4\n"), -			wantMap: map[string][]string{ -				"k1": []string{"v1"}, -				"k2": []string{"v2", "v3=4"}, -			}, -		}, -	} { -		msg, err := NewMessageASCII(table.input, len(table.wantMap)) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := msg.m, table.wantMap; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestNumField(t *testing.T)           {} -func TestGetStrings(t *testing.T)         {} -func TestGetString(t *testing.T)          {} -func TestGetUint64(t *testing.T)          {} -func TestGetHash(t *testing.T)            {} -func TestGetSignature(t *testing.T)       {} -func TestGetVerificationKey(t *testing.T) {} -func TestDecodeHex(t *testing.T)          {} - -/* - * - * MarshalASCII methods and helpers - * - */ -func TestLeafMarshalASCII(t *testing.T) { -	description := "valid: two leaves" -	leafList := []*Leaf{ -		&Leaf{ -			Message: Message{ -				ShardHint: 123, -				Checksum:  testBuffer32, -			}, -			SigIdent: SigIdent{ -				Signature: testBuffer64, -				KeyHash:   testBuffer32, -			}, -		}, -		&Leaf{ -			Message: Message{ -				ShardHint: 456, -				Checksum:  testBuffer32, -			}, -			SigIdent: SigIdent{ -				Signature: testBuffer64, -				KeyHash:   testBuffer32, -			}, -		}, -	} -	wantBuf := bytes.NewBufferString(fmt.Sprintf( -		"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+ -			"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s", -		// Leaf 1 -		ShardHint, Delim, 123, EOL, -		Checksum, Delim, testBuffer32[:], EOL, -		Signature, Delim, testBuffer64[:], EOL, -		KeyHash, Delim, testBuffer32[:], EOL, -		// Leaf 2 -		ShardHint, Delim, 456, EOL, -		Checksum, Delim, testBuffer32[:], EOL, -		Signature, Delim, testBuffer64[:], EOL, -		KeyHash, Delim, testBuffer32[:], EOL, -	)) -	buf := bytes.NewBuffer(nil) -	for _, leaf := range leafList { -		if err := leaf.MarshalASCII(buf); err != nil { -			t.Errorf("expected error %v but got %v in test %q: %v", false, true, description, err) -			return -		} -	} -	if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { -		t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) -	} -} - -func TestSignedTreeHeadMarshalASCII(t *testing.T) { -	description := "valid" -	sth := &SignedTreeHead{ -		TreeHead: TreeHead{ -			Timestamp: 123, -			TreeSize:  456, -			RootHash:  testBuffer32, -		}, -		Signature: testBuffer64, -	} -	wantBuf := bytes.NewBufferString(fmt.Sprintf( -		"%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", -		Timestamp, Delim, 123, EOL, -		TreeSize, Delim, 456, EOL, -		RootHash, Delim, testBuffer32[:], EOL, -		Signature, Delim, testBuffer64[:], EOL, -	)) -	buf := bytes.NewBuffer(nil) -	if err := sth.MarshalASCII(buf); err != nil { -		t.Errorf("expected error %v but got %v in test %q", false, true, description) -		return -	} -	if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { -		t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) -	} -} - -func TestInclusionProofMarshalASCII(t *testing.T) { -	description := "valid" -	proof := InclusionProof{ -		TreeSize:  321, -		LeafIndex: 123, -		Path: []*[HashSize]byte{ -			testBuffer32, -			testBuffer32, -		}, -	} -	wantBuf := bytes.NewBufferString(fmt.Sprintf( -		"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", -		LeafIndex, Delim, 123, EOL, -		InclusionPath, Delim, testBuffer32[:], EOL, -		InclusionPath, Delim, testBuffer32[:], EOL, -	)) -	buf := bytes.NewBuffer(nil) -	if err := proof.MarshalASCII(buf); err != nil { -		t.Errorf("expected error %v but got %v in test %q", false, true, description) -		return -	} -	if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { -		t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) -	} -} - -func TestConsistencyProofMarshalASCII(t *testing.T) { -	description := "valid" -	proof := ConsistencyProof{ -		NewSize: 321, -		OldSize: 123, -		Path: []*[HashSize]byte{ -			testBuffer32, -			testBuffer32, -		}, -	} -	wantBuf := bytes.NewBufferString(fmt.Sprintf( -		"%s%s%x%s"+"%s%s%x%s", -		ConsistencyPath, Delim, testBuffer32[:], EOL, -		ConsistencyPath, Delim, testBuffer32[:], EOL, -	)) -	buf := bytes.NewBuffer(nil) -	if err := proof.MarshalASCII(buf); err != nil { -		t.Errorf("expected error %v but got %v in test %q", false, true, description) -		return -	} -	if got, want := buf.Bytes(), wantBuf.Bytes(); !bytes.Equal(got, want) { -		t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", string(got), string(want), description) -	} -} - -func TestWriteASCII(t *testing.T) { -} - -/* - * - * UnmarshalASCII methods and helpers - * - */ -func TestLeafListUnmarshalASCII(t *testing.T) {} - -func TestSignedTreeHeadUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantSth     *SignedTreeHead -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%d%s"+"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s", -				Timestamp, Delim, 123, EOL, -				TreeSize, Delim, 456, EOL, -				RootHash, Delim, testBuffer32[:], EOL, -				Signature, Delim, testBuffer64[:], EOL, -			)), -			wantSth: &SignedTreeHead{ -				TreeHead: TreeHead{ -					Timestamp: 123, -					TreeSize:  456, -					RootHash:  testBuffer32, -				}, -				Signature: testBuffer64, -			}, -		}, -	} { -		var sth SignedTreeHead -		err := sth.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &sth, table.wantSth; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestInclusionProofUnmarshalASCII(t *testing.T)   {} -func TestConsistencyProofUnmarshalASCII(t *testing.T) {} - -func TestInclusionProofRequestUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantReq     *InclusionProofRequest -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%x%s"+"%s%s%d%s", -				LeafHash, Delim, testBuffer32[:], EOL, -				TreeSize, Delim, 123, EOL, -			)), -			wantReq: &InclusionProofRequest{ -				LeafHash: testBuffer32, -				TreeSize: 123, -			}, -		}, -	} { -		var req InclusionProofRequest -		err := req.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestConsistencyProofRequestUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantReq     *ConsistencyProofRequest -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%d%s"+"%s%s%d%s", -				NewSize, Delim, 321, EOL, -				OldSize, Delim, 123, EOL, -			)), -			wantReq: &ConsistencyProofRequest{ -				NewSize: 321, -				OldSize: 123, -			}, -		}, -	} { -		var req ConsistencyProofRequest -		err := req.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestLeavesRequestUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantReq     *LeavesRequest -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%d%s"+"%s%s%d%s", -				StartSize, Delim, 123, EOL, -				EndSize, Delim, 456, EOL, -			)), -			wantReq: &LeavesRequest{ -				StartSize: 123, -				EndSize:   456, -			}, -		}, -	} { -		var req LeavesRequest -		err := req.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestLeafRequestUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantReq     *LeafRequest -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%d%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%x%s"+"%s%s%s%s", -				ShardHint, Delim, 123, EOL, -				Checksum, Delim, testBuffer32[:], EOL, -				Signature, Delim, testBuffer64[:], EOL, -				VerificationKey, Delim, testBuffer32[:], EOL, -				DomainHint, Delim, "example.com", EOL, -			)), -			wantReq: &LeafRequest{ -				Message: Message{ -					ShardHint: 123, -					Checksum:  testBuffer32, -				}, -				Signature:       testBuffer64, -				VerificationKey: testBuffer32, -				DomainHint:      "example.com", -			}, -		}, -	} { -		var req LeafRequest -		err := req.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} - -func TestCosignatureRequestUnmarshalASCII(t *testing.T) { -	for _, table := range []struct { -		description string -		buf         io.Reader -		wantErr     bool -		wantReq     *CosignatureRequest -	}{ -		{ -			description: "valid", -			buf: bytes.NewBufferString(fmt.Sprintf( -				"%s%s%x%s"+"%s%s%x%s", -				Cosignature, Delim, testBuffer64[:], EOL, -				KeyHash, Delim, testBuffer32[:], EOL, -			)), -			wantReq: &CosignatureRequest{ -				SigIdent: SigIdent{ -					Signature: testBuffer64, -					KeyHash:   testBuffer32, -				}, -			}, -		}, -	} { -		var req CosignatureRequest -		err := req.UnmarshalASCII(table.buf) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &req, table.wantReq; !reflect.DeepEqual(got, want) { -			t.Errorf("got\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) -		} -	} -} diff --git a/pkg/types/trunnel.go b/pkg/types/trunnel.go deleted file mode 100644 index 5350c5b..0000000 --- a/pkg/types/trunnel.go +++ /dev/null @@ -1,63 +0,0 @@ -package types - -import ( -	"encoding/binary" -	"fmt" -) - -const ( -	// MessageSize is the number of bytes in a Trunnel-encoded leaf message -	MessageSize = 8 + HashSize -	// LeafSize is the number of bytes in a Trunnel-encoded leaf -	LeafSize = MessageSize + SignatureSize + HashSize -	// TreeHeadSize is the number of bytes in a Trunnel-encoded tree head -	TreeHeadSize = 8 + 8 + HashSize + HashSize -) - -// Marshal returns a Trunnel-encoded message -func (m *Message) Marshal() []byte { -	buf := make([]byte, MessageSize) -	binary.BigEndian.PutUint64(buf, m.ShardHint) -	copy(buf[8:], m.Checksum[:]) -	return buf -} - -// Marshal returns a Trunnel-encoded leaf -func (l *Leaf) Marshal() []byte { -	buf := l.Message.Marshal() -	buf = append(buf, l.SigIdent.Signature[:]...) -	buf = append(buf, l.SigIdent.KeyHash[:]...) -	return buf -} - -// Marshal returns a Trunnel-encoded tree head -func (th *TreeHead) Marshal() []byte { -	buf := make([]byte, TreeHeadSize) -	binary.BigEndian.PutUint64(buf[0:8], th.Timestamp) -	binary.BigEndian.PutUint64(buf[8:16], th.TreeSize) -	copy(buf[16:16+HashSize], th.RootHash[:]) -	copy(buf[16+HashSize:], th.KeyHash[:]) -	return buf -} - -// Unmarshal parses the Trunnel-encoded buffer as a leaf -func (l *Leaf) Unmarshal(buf []byte) error { -	if len(buf) != LeafSize { -		return fmt.Errorf("invalid leaf size: %v", len(buf)) -	} -	// Shard hint -	l.ShardHint = binary.BigEndian.Uint64(buf) -	offset := 8 -	// Checksum -	l.Checksum = &[HashSize]byte{} -	copy(l.Checksum[:], buf[offset:offset+HashSize]) -	offset += HashSize -	// Signature -	l.Signature = &[SignatureSize]byte{} -	copy(l.Signature[:], buf[offset:offset+SignatureSize]) -	offset += SignatureSize -	// KeyHash -	l.KeyHash = &[HashSize]byte{} -	copy(l.KeyHash[:], buf[offset:]) -	return nil -} diff --git a/pkg/types/trunnel_test.go b/pkg/types/trunnel_test.go deleted file mode 100644 index a3ae1ba..0000000 --- a/pkg/types/trunnel_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package types - -import ( -	"bytes" -	"reflect" -	"testing" -) - -var ( -	testBuffer32 = &[32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} -	testBuffer64 = &[64]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} -) - -func TestMarshalMessage(t *testing.T) { -	description := "valid: shard hint 72623859790382856, checksum 0x00,0x01,..." -	message := &Message{ -		ShardHint: 72623859790382856, -		Checksum:  testBuffer32, -	} -	want := bytes.Join([][]byte{ -		[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, -		testBuffer32[:], -	}, nil) -	if got := message.Marshal(); !bytes.Equal(got, want) { -		t.Errorf("got message\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) -	} -} - -func TestMarshalLeaf(t *testing.T) { -	description := "valid: shard hint 72623859790382856, buffers 0x00,0x01,..." -	leaf := &Leaf{ -		Message: Message{ -			ShardHint: 72623859790382856, -			Checksum:  testBuffer32, -		}, -		SigIdent: SigIdent{ -			Signature: testBuffer64, -			KeyHash:   testBuffer32, -		}, -	} -	want := bytes.Join([][]byte{ -		[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, -		testBuffer32[:], testBuffer64[:], testBuffer32[:], -	}, nil) -	if got := leaf.Marshal(); !bytes.Equal(got, want) { -		t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) -	} -} - -func TestMarshalTreeHead(t *testing.T) { -	description := "valid: timestamp 16909060, tree size 72623859790382856, root hash & key hash 0x00,0x01,..." -	th := &TreeHead{ -		Timestamp: 16909060, -		TreeSize:  72623859790382856, -		RootHash:  testBuffer32, -		KeyHash:   testBuffer32, -	} -	want := bytes.Join([][]byte{ -		[]byte{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}, -		[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, -		testBuffer32[:], -		testBuffer32[:], -	}, nil) -	if got := th.Marshal(); !bytes.Equal(got, want) { -		t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, description) -	} -} - -func TestUnmarshalLeaf(t *testing.T) { -	for _, table := range []struct { -		description string -		serialized  []byte -		wantErr     bool -		want        *Leaf -	}{ -		{ -			description: "invalid: not enough bytes", -			serialized:  make([]byte, LeafSize-1), -			wantErr:     true, -		}, -		{ -			description: "invalid: too many bytes", -			serialized:  make([]byte, LeafSize+1), -			wantErr:     true, -		}, -		{ -			description: "valid: shard hint 72623859790382856, buffers 0x00,0x01,...", -			serialized: bytes.Join([][]byte{ -				[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, -				testBuffer32[:], testBuffer64[:], testBuffer32[:], -			}, nil), -			want: &Leaf{ -				Message: Message{ -					ShardHint: 72623859790382856, -					Checksum:  testBuffer32, -				}, -				SigIdent: SigIdent{ -					Signature: testBuffer64, -					KeyHash:   testBuffer32, -				}, -			}, -		}, -	} { -		var leaf Leaf -		err := leaf.Unmarshal(table.serialized) -		if got, want := err != nil, table.wantErr; got != want { -			t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) -		} -		if err != nil { -			continue -		} -		if got, want := &leaf, table.want; !reflect.DeepEqual(got, want) { -			t.Errorf("got leaf\n\t%v\nbut wanted\n\t%v\nin test %q\n", got, want, table.description) -		} -	} -} diff --git a/pkg/types/types.go b/pkg/types/types.go deleted file mode 100644 index bc58c98..0000000 --- a/pkg/types/types.go +++ /dev/null @@ -1,138 +0,0 @@ -package types - -import ( -	"crypto" -	"crypto/ed25519" -	"crypto/sha256" -	"fmt" -	"strings" -) - -const ( -	HashSize            = sha256.Size -	SignatureSize       = ed25519.SignatureSize -	VerificationKeySize = ed25519.PublicKeySize - -	EndpointAddLeaf             = Endpoint("add-leaf") -	EndpointAddCosignature      = Endpoint("add-cosignature") -	EndpointGetTreeHeadLatest   = Endpoint("get-tree-head-latest") -	EndpointGetTreeHeadToSign   = Endpoint("get-tree-head-to-sign") -	EndpointGetTreeHeadCosigned = Endpoint("get-tree-head-cosigned") -	EndpointGetInclusionProof   = Endpoint("get-inclusion-proof") -	EndpointGetConsistencyProof = Endpoint("get-consistency-proof") -	EndpointGetLeaves           = Endpoint("get-leaves") -) - -// Endpoint is a named HTTP API endpoint -type Endpoint string - -// Path joins a number of components to form a full endpoint path.  For example, -// EndpointAddLeaf.Path("example.com", "st/v0") -> example.com/st/v0/add-leaf. -func (e Endpoint) Path(components ...string) string { -	return strings.Join(append(components, string(e)), "/") -} - -type Leaf struct { -	Message -	SigIdent -} - -type Message struct { -	ShardHint uint64 -	Checksum  *[HashSize]byte -} - -type SigIdent struct { -	Signature *[SignatureSize]byte -	KeyHash   *[HashSize]byte -} - -type SignedTreeHead struct { -	TreeHead -	Signature *[SignatureSize]byte -} - -type CosignedTreeHead struct { -	SignedTreeHead -	SigIdent []*SigIdent -} - -type TreeHead struct { -	Timestamp uint64 -	TreeSize  uint64 -	RootHash  *[HashSize]byte -	KeyHash   *[HashSize]byte -} - -type ConsistencyProof struct { -	NewSize uint64 -	OldSize uint64 -	Path    []*[HashSize]byte -} - -type InclusionProof struct { -	TreeSize  uint64 -	LeafIndex uint64 -	Path      []*[HashSize]byte -} - -type LeafList []*Leaf - -type ConsistencyProofRequest struct { -	NewSize uint64 -	OldSize uint64 -} - -type InclusionProofRequest struct { -	LeafHash *[HashSize]byte -	TreeSize uint64 -} - -type LeavesRequest struct { -	StartSize uint64 -	EndSize   uint64 -} - -type LeafRequest struct { -	Message -	Signature       *[SignatureSize]byte -	VerificationKey *[VerificationKeySize]byte -	DomainHint      string -} - -type CosignatureRequest struct { -	SigIdent -} - -// Sign signs the tree head using the log's signature scheme -func (th *TreeHead) Sign(signer crypto.Signer) (*SignedTreeHead, error) { -	sig, err := signer.Sign(nil, th.Marshal(), crypto.Hash(0)) -	if err != nil { -		return nil, fmt.Errorf("Sign: %v", err) -	} - -	sth := &SignedTreeHead{ -		TreeHead:  *th, -		Signature: &[SignatureSize]byte{}, -	} -	copy(sth.Signature[:], sig) -	return sth, nil -} - -// Verify verifies the tree head signature using the log's signature scheme -func (th *TreeHead) Verify(vk *[VerificationKeySize]byte, sig *[SignatureSize]byte) error { -	if !ed25519.Verify(ed25519.PublicKey(vk[:]), th.Marshal(), sig[:]) { -		return fmt.Errorf("invalid tree head signature") -	} -	return nil -} - -// Verify checks if a leaf is included in the log -func (p *InclusionProof) Verify(leaf *Leaf, th *TreeHead) error { // TODO -	return nil -} - -// Verify checks if two tree heads are consistent -func (p *ConsistencyProof) Verify(oldTH, newTH *TreeHead) error { // TODO -	return nil -} diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go deleted file mode 100644 index d823ea2..0000000 --- a/pkg/types/types_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import ( -	"testing" -) - -func TestEndpointPath(t *testing.T) { -	base, prefix, proto := "example.com", "log", "sigsum/v0" -	for _, table := range []struct { -		endpoint Endpoint -		want     string -	}{ -		{ -			endpoint: EndpointAddLeaf, -			want:     "example.com/log/sigsum/v0/add-leaf", -		}, -		{ -			endpoint: EndpointAddCosignature, -			want:     "example.com/log/sigsum/v0/add-cosignature", -		}, -		{ -			endpoint: EndpointGetTreeHeadLatest, -			want:     "example.com/log/sigsum/v0/get-tree-head-latest", -		}, -		{ -			endpoint: EndpointGetTreeHeadToSign, -			want:     "example.com/log/sigsum/v0/get-tree-head-to-sign", -		}, -		{ -			endpoint: EndpointGetTreeHeadCosigned, -			want:     "example.com/log/sigsum/v0/get-tree-head-cosigned", -		}, -		{ -			endpoint: EndpointGetConsistencyProof, -			want:     "example.com/log/sigsum/v0/get-consistency-proof", -		}, -		{ -			endpoint: EndpointGetInclusionProof, -			want:     "example.com/log/sigsum/v0/get-inclusion-proof", -		}, -		{ -			endpoint: EndpointGetLeaves, -			want:     "example.com/log/sigsum/v0/get-leaves", -		}, -	} { -		if got, want := table.endpoint.Path(base+"/"+prefix+"/"+proto), table.want; got != want { -			t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\twith one component", got, want) -		} -		if got, want := table.endpoint.Path(base, prefix, proto), table.want; got != want { -			t.Errorf("got endpoint\n%s\n\tbut wanted\n%s\n\tmultiple components", got, want) -		} -	} -} - -func TestTreeHeadSign(t *testing.T)           {} -func TestTreeHeadVerify(t *testing.T)         {} -func TestInclusionProofVerify(t *testing.T)   {} -func TestConsistencyProofVerify(t *testing.T) {} diff --git a/pkg/types/util.go b/pkg/types/util.go deleted file mode 100644 index 3cd7dfa..0000000 --- a/pkg/types/util.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( -	"crypto/sha256" -) - -const ( -	LeafHashPrefix = 0x00 -) - -func Hash(buf []byte) *[HashSize]byte { -	var ret [HashSize]byte -	hash := sha256.New() -	hash.Write(buf) -	copy(ret[:], hash.Sum(nil)) -	return &ret -} - -func HashLeaf(buf []byte) *[HashSize]byte { -	return Hash(append([]byte{LeafHashPrefix}, buf...)) -} | 
