diff options
author | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 19:53:54 +0100 |
---|---|---|
committer | Rasmus Dahlberg <rasmus@mullvad.net> | 2021-12-20 19:53:54 +0100 |
commit | dda238b9fc105219f220f0ec3b341b0c81b71301 (patch) | |
tree | edbbb787ccd1c1816edfa44caf749c8be68b7bf9 | |
parent | 5ba4a77233549819440cc41a02503f3a85213e24 (diff) |
types: Start using sigsum-lib-go
This commit does not change the way in which the log behaves externally.
In other words, all changes are internal and involves renaming and code
restructuring. Most notably picking up the refactored sigsum-lib-go.
-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...)) -} |