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...)) -} |