diff options
Diffstat (limited to 'pkg/client')
-rw-r--r-- | pkg/client/api/api.go | 22 | ||||
-rw-r--r-- | pkg/client/api/network_client.go | 133 | ||||
-rw-r--r-- | pkg/client/submitter.go | 129 |
3 files changed, 284 insertions, 0 deletions
diff --git a/pkg/client/api/api.go b/pkg/client/api/api.go new file mode 100644 index 0000000..d773bcc --- /dev/null +++ b/pkg/client/api/api.go @@ -0,0 +1,22 @@ +// package api implements the Sigsum v0 API. See +// +// https://git.sigsum.org/sigsum/tree/doc/api.md +// +// for further details. No verification, retries, etc., is done here. +package api + +import ( + "git.sigsum.org/sigsum-lib-go/pkg/requests" + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type API interface { + GetToCosignTreeHead() (*types.SignedTreeHead, error) + GetCosignedTreeHead() (*types.CosignedTreeHead, error) + GetInclusionProof(treeSize uint64, leafHash *types.Hash) (*types.InclusionProof, error) + GetConsistencyProof(oldSize, newSize uint64) (*types.ConsistencyProof, error) + GetLeaves(startSize, endSize uint64) (*types.Leaves, error) + + AddLeaf(*requests.Leaf) error + AddCosignature(*requests.Cosignature) error +} diff --git a/pkg/client/api/network_client.go b/pkg/client/api/network_client.go new file mode 100644 index 0000000..f7baf76 --- /dev/null +++ b/pkg/client/api/network_client.go @@ -0,0 +1,133 @@ +package api + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + + "git.sigsum.org/sigsum-lib-go/pkg/requests" + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type NetworkClient struct { + http.Client + LogURL string +} + +func NewNetworkClient(httpClient http.Client, logURL string) *NetworkClient { + return &NetworkClient{ + Client: httpClient, + LogURL: logURL, + } +} + +func (c *NetworkClient) GetToCosignTreeHead() (*types.SignedTreeHead, error) { + return nil, fmt.Errorf("TODO") +} + +func (c *NetworkClient) GetCosignedTreeHead() (*types.CosignedTreeHead, error) { + rsp, err := c.get(types.EndpointGetTreeHeadCosigned.Path(c.LogURL)) + if err != nil { + return nil, fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + + defer rsp.Body.Close() + if rsp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + + var cth types.CosignedTreeHead + if err := cth.FromASCII(rsp.Body); err != nil { + return nil, fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + return &cth, nil +} + +func (c *NetworkClient) GetInclusionProof(treeSize uint64, leafHash *types.Hash) (*types.InclusionProof, error) { + data := requests.InclusionProof{ + TreeSize: treeSize, + LeafHash: *leafHash, + } + buf := bytes.NewBuffer(nil) + if err := data.ToASCII(buf); err != nil { + return nil, fmt.Errorf("client: invalid request data: %v", err) + } + + rsp, err := c.post(types.EndpointGetInclusionProof.Path(c.LogURL), buf) + if err != nil { + return nil, fmt.Errorf("client: invalid proof request: %v", err) + } + + defer rsp.Body.Close() + if rsp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + var proof types.InclusionProof + if err := proof.FromASCII(rsp.Body, treeSize); err != nil { + return nil, fmt.Errorf("client: failed parsing response: %v", err) + } + + return &proof, nil +} + +func (c *NetworkClient) GetConsistencyProof(oldSize, newSize uint64) (*types.ConsistencyProof, error) { + return nil, fmt.Errorf("TODO") +} + +func (c *NetworkClient) GetLeaves(startSize, endSize uint64) (*types.Leaves, error) { + return nil, fmt.Errorf("TODO") +} + +func (c *NetworkClient) AddLeaf(leaf *requests.Leaf) error { + buf := bytes.NewBuffer(nil) + if err := leaf.ToASCII(buf); err != nil { + return fmt.Errorf("client: invalid request data: %v", err) + } + + rsp, err := c.post(types.EndpointAddLeaf.Path(c.LogURL), buf) + if err != nil { + return fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + + defer rsp.Body.Close() + if rsp.StatusCode != http.StatusOK { + return fmt.Errorf("client: HTTP %d: %s", rsp.StatusCode, errorMessage(rsp.Body)) + } + return nil +} + +func (c *NetworkClient) AddCosignature(*requests.Cosignature) error { + return fmt.Errorf("TODO") +} + +func (c *NetworkClient) get(url string) (*http.Response, error) { + log.Printf("GET %s", url) + return c.Get(url) +} + +func (c *NetworkClient) post(url string, buf *bytes.Buffer) (*http.Response, error) { + req, err := http.NewRequest("POST", url, buf) + if err != nil { + return nil, fmt.Errorf("client: invalid leaf request: %v", err) + } + + log.Printf("POST %s", url) + return c.Do(req) +} + +func errorMessage(r io.Reader) string { + b, _ := ioutil.ReadAll(r) + return string(b) + + // TODO: the below doesn't work because log error messages are malformed + //msg := struct { + // Message string `ascii:"Error"` + //}{} + //if err := ascii.StdEncoding.Deserialize(r, &msg); err != nil { + // return fmt.Sprintf("malformed error message: %v", err) + //} + //return msg.Message +} diff --git a/pkg/client/submitter.go b/pkg/client/submitter.go new file mode 100644 index 0000000..f03e66a --- /dev/null +++ b/pkg/client/submitter.go @@ -0,0 +1,129 @@ +package client + +import ( + "context" + "fmt" + "io" + "log" + "net/http" + "time" + + "git.sigsum.org/sigsum-lib-go/pkg/requests" + "git.sigsum.org/sigsum-lib-go/pkg/types" + "git.sigsum.org/sigsum-tool-go/pkg/client/api" + "git.sigsum.org/sigsum-tool-go/pkg/policy" +) + +type Submitter interface { + AddLeaves(context.Context, []requests.Leaf) ([]Bundle, error) +} + +type SubmitClient struct { + api api.API + policy policy.Policy +} + +// TODO: should signature+key_hash (not) be in bundle? +type Bundle struct { + ShardHint uint64 + Signature types.Signature + KeyHash types.Hash + InclusionProof types.InclusionProof + CosignedTreeHead types.CosignedTreeHead +} + +func NewSubmitClient(policy policy.Policy) *SubmitClient { + return &SubmitClient{ + policy: policy, + } +} + +// TODO: feedback on the below sketch; improve it; implement properly. +// 0. Select one log in policy, setup API network client. +// 1. Loop over all leaves that have yet to received 200 OK. Move on to +// the next leaf on a non-200 status code, trying again next itteration. +// 3. Try to fetch an inclusion proof for the latest HTTP 200 OK +// response every time a new cosigned tree head becomes available. +// Output warning if more than two cosigned tree heads pass. +// 4. Loop over all leaves and fetch inclusion proofs for X. +// [Exit with error if any inclusion proof is not available / invalid] +// 5. Return bundles in the same order as leaves were passed. +func (sc *SubmitClient) AddLeaves(_ context.Context, leaves []requests.Leaf) ([]Bundle, error) { + if err := sc.newAPI(); err != nil { + return nil, fmt.Errorf("client: %v", err) + } + + for _, leaf := range leaves { + err := sc.api.AddLeaf(&leaf) + if err != nil { + return nil, fmt.Errorf("client: %v", err) + } + } + + start := time.Now().Unix() + var bundles []Bundle + for { + cth, err := sc.api.GetCosignedTreeHead() + if err != nil { + return nil, fmt.Errorf("client: %v", err) + } + // TODO: verify that cth is valid for policy + + ok := true + bundles = nil + for _, leaf := range leaves { + l := types.Leaf{ + Statement: types.Statement{ + ShardHint: leaf.ShardHint, + Checksum: leaf.Checksum, + }, + Signature: leaf.Signature, + KeyHash: *types.HashFn(leaf.VerificationKey[:]), + } + lh := types.HashFn(append([]byte{0x00}, l.ToBinary()...)) + log.Printf("leaf hash is: %x", lh[:]) + + proof, err := sc.api.GetInclusionProof(cth.TreeSize, lh) + if err != nil { + log.Printf("no inclusion proof for tree size %d yet, please wait.\n", cth.TreeSize) + ok = false + break + } + + // TODO: verify that inclusion proof is valid + bundles = append(bundles, Bundle{ + ShardHint: l.ShardHint, + Signature: l.Signature, + KeyHash: l.KeyHash, + InclusionProof: *proof, + CosignedTreeHead: *cth, + }) + } + if ok { + break + } + + time.Sleep(15 * time.Second) + log.Printf("waited %d seconds...\n", time.Now().Unix()-start) + } + + return bundles, nil +} + +func (sc *SubmitClient) newAPI() error { + // TODO: select a log properly. + sc.api = api.NewNetworkClient(http.Client{}, sc.policy.Logs()[0].LogURL) + return nil +} + +func (b *Bundle) ToASCII(w io.Writer) error { + return fmt.Errorf("TODO") +} + +func (b *Bundle) FromASCII(r io.Reader) error { + return fmt.Errorf("TODO") +} + +func (b *Bundle) Verify(k *types.PublicKey, p *policy.Policy, r io.Reader) error { + return fmt.Errorf("TODO") +} |