diff options
-rw-r--r-- | LICENSE | 25 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | cmd/sigsum/cmd.go | 131 | ||||
-rw-r--r-- | cmd/sigsum/main.go | 116 | ||||
-rw-r--r-- | cmd/sigsum/test/keys/signify.pub | 2 | ||||
-rw-r--r-- | cmd/sigsum/test/keys/signify.sec | 2 | ||||
-rwxr-xr-x | cmd/sigsum/test/signify.sh | 55 | ||||
-rw-r--r-- | go.mod | 5 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-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 | ||||
-rw-r--r-- | pkg/policy/default.go | 66 | ||||
-rw-r--r-- | pkg/policy/policy.go | 32 | ||||
-rw-r--r-- | pkg/signatures/minisign/minisign.go | 22 | ||||
-rw-r--r-- | pkg/signatures/signatures.go | 13 | ||||
-rw-r--r-- | pkg/signatures/signify/signify.go | 91 | ||||
-rw-r--r-- | pkg/signatures/ssh/ssh.go | 22 |
18 files changed, 870 insertions, 0 deletions
@@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2021, The Sigsum Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..124960a --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# sigsum-tool-go +Mock-up / work-in-progress tooling. May be moved somewhere else. diff --git a/cmd/sigsum/cmd.go b/cmd/sigsum/cmd.go new file mode 100644 index 0000000..70a1c51 --- /dev/null +++ b/cmd/sigsum/cmd.go @@ -0,0 +1,131 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "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" + "git.sigsum.org/sigsum-tool-go/pkg/policy" + "git.sigsum.org/sigsum-tool-go/pkg/signatures" + "git.sigsum.org/sigsum-tool-go/pkg/signatures/minisign" + "git.sigsum.org/sigsum-tool-go/pkg/signatures/signify" + "git.sigsum.org/sigsum-tool-go/pkg/signatures/ssh" +) + +func cmdVerify(args []string, policy policy.Policy, optVerifyType, optVerifyKey string) error { + return fmt.Errorf("TODO") +} + +func cmdBundle(args []string, policy policy.Policy, optBundleType, optBundleKey, optBundleDomainHint string) error { + if len(args) == 0 { + return fmt.Errorf("bundle: need at least one file") + } + + var parser signatures.Parser + switch optBundleType { + case "signify": + parser = &signify.Parser{} + case "minisign": + parser = &minisign.Parser{} + case "ssh": + parser = &ssh.Parser{} + default: + return fmt.Errorf("bundle: invalid key type %q", optBundleType) + } + + b, err := ioutil.ReadFile(optBundleKey) + if err != nil { + return fmt.Errorf("bundle: failed reading file %q: %v", optBundleKey, err) + } + pub, err := parser.PublicKey(bytes.NewBuffer(b)) + if err != nil { + return fmt.Errorf("bundle: %v", err) + } + // TODO: check that domain hint is valid for public key + + var reqs []requests.Leaf + for _, path := range args { + checksum, err := fileHash(path) + if err != nil { + return fmt.Errorf("bundle: %v", err) + } + + sigPath := path + parser.SignatureSuffix() + b, err := ioutil.ReadFile(sigPath) + if err != nil { + return fmt.Errorf("bundle: failed reading file %q: %v", sigPath, err) + } + sig, err := parser.Signature(bytes.NewBuffer(b)) + if err != nil { + return fmt.Errorf("bundle: %v", err) + } + + req := requests.Leaf{ + Statement: types.Statement{ + ShardHint: policy.ShardHint(), + Checksum: *checksum, + }, + Signature: *sig, + VerificationKey: *pub, + DomainHint: optBundleDomainHint, + } + if !req.Statement.Verify(&req.VerificationKey, &req.Signature) { + return fmt.Errorf("bundle: invalid signature for file %q", path) + } + reqs = append(reqs, req) + } + + sc := client.NewSubmitClient(policy) + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) + defer cancel() + bundles, err := sc.AddLeaves(ctx, reqs) + if err != nil { + return fmt.Errorf("bundle: %v", err) + } + + // TODO: verify bundles + // TODO: write to files + fmt.Printf("got %d bundles\n", len(bundles)) + return nil +} + +func cmdFormat(args []string, policy policy.Policy) error { + if len(args) != 1 { + return fmt.Errorf("format: need exactly one file") + } + + checksum, err := fileHash(args[0]) + if err != nil { + return fmt.Errorf("format: %v", err) + } + stm := types.Statement{ + ShardHint: policy.ShardHint(), + Checksum: *checksum, + } + + fmt.Printf("%s", stm.ToBinary()) + return nil +} + +func cmdNamespace(args []string, policy policy.Policy) error { + if len(args) != 0 { + return fmt.Errorf("namespace: got trailing arguments") + } + + fmt.Printf("tree_leaf:v0:%d@sigsum.org", policy.ShardHint()) + return nil +} + +// TODO: don't read full file into memory at once +func fileHash(path string) (*types.Hash, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed reading file %q", path) + } + return types.HashFn(b), nil +} diff --git a/cmd/sigsum/main.go b/cmd/sigsum/main.go new file mode 100644 index 0000000..460eba3 --- /dev/null +++ b/cmd/sigsum/main.go @@ -0,0 +1,116 @@ +// package main provides a tool named `sigsum`. +// +// Build as follows: +// +// $ go build -ldflags="-X 'main.someVersion=git commit $(git rev-list -1 HEAD)'" +// +// Install as follows: +// +// $ go install -ldflags="-X 'main.someVersion=git commit $(git rev-list -1 HEAD)'" +// +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "git.sigsum.org/sigsum-tool-go/pkg/policy" +) + +const usage = `sigsum version %s + +Usage: + sigsum help + Output usage message. + + sigsum verify -t TYPE -k PUBLIC_KEY FILE + Verify that a file's signed checksum is public and valid. + -t, --type Signature format (Available options: signify, minisign, ssh) + -k, --key Path to a public key. + + sigsum bundle -t TYPE -k PUBLIC_KEY -d DOMAIN_HINT FILE... + Perform logging request(s) and write inclusion proof bundle(s). + -t, --type Signature format (Available options: signify, minisign, ssh) + -k, --key Path to a public key. + -d, --domain-hint Domain name that is aware of the public key. + + sigsum format FILE + Output bytes to be Ed25519-signed. + + sigsum namespace + Output namespace to be used in SSH signing context. + +Transparency log proofs and signatures must be located at $FILE.sigsum.v0. +Signatures must be located at $FILE.{sig,minisig}, depending on -t TYPE. + +` + +var ( + optBundleType, optBundleKey, optBundleDomainHint string + optVerifyType, optVerifyKey string + + someVersion = "unknown" +) + +func main() { + log.SetFlags(0) + + var err error + var defaultPolicy policy.DefaultPolicy + switch cmd := parseCommand(); cmd.Name() { + case "help": + cmd.Usage() + case "verify": + err = cmdVerify(cmd.Args(), &defaultPolicy, optVerifyType, optVerifyKey) + case "bundle": + err = cmdBundle(cmd.Args(), &defaultPolicy, optBundleType, optBundleKey, optBundleDomainHint) + case "format": + err = cmdFormat(cmd.Args(), &defaultPolicy) + case "namespace": + err = cmdNamespace(cmd.Args(), &defaultPolicy) + default: + err = fmt.Errorf("invalid command %q, try %q", cmd.Name(), "sigsum help") + } + + if err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} + +func parseCommand() (fs *flag.FlagSet) { + args := os.Args + if len(args) < 2 { + args = append(args, "") + } + defer func() { + registerOptions(fs) + fs.Usage = func() { + log.Printf(usage, someVersion) + } + fs.Parse(args) + }() + + fs = flag.NewFlagSet(args[1], flag.ExitOnError) + args = args[2:] + return +} + +func registerOptions(fs *flag.FlagSet) { + switch cmd := fs.Name(); cmd { + case "verify": + registerStringOption(fs, &optVerifyType, "t", "type", "") + registerStringOption(fs, &optVerifyKey, "k", "key", "") + case "bundle": + registerStringOption(fs, &optBundleType, "t", "type", "") + registerStringOption(fs, &optBundleKey, "k", "key", "") + registerStringOption(fs, &optBundleDomainHint, "d", "domain-hint", "") + } +} + +func registerStringOption(fs *flag.FlagSet, opt *string, short, long, value string) { + fs.StringVar(opt, short, value, "") + fs.StringVar(opt, long, value, "") +} diff --git a/cmd/sigsum/test/keys/signify.pub b/cmd/sigsum/test/keys/signify.pub new file mode 100644 index 0000000..742a66a --- /dev/null +++ b/cmd/sigsum/test/keys/signify.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQhuW/GnP7W13NSC8qzkpnB1BJXk96/GhaWe6f/OpBvMRHFdwuUIYGb diff --git a/cmd/sigsum/test/keys/signify.sec b/cmd/sigsum/test/keys/signify.sec new file mode 100644 index 0000000..57cdf84 --- /dev/null +++ b/cmd/sigsum/test/keys/signify.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAACrUdp2uXyio8Rdwv0W6PLGiUQei6JeOZAYhuW/GnP7W1655JdycJo4tbOh/ba1OxA7QyVSdNFBs5SyF4eM5yIE98xhTAtizBDxki1Y3sqcFvWFH8ZlKzRjY8rUrTYaaCQE= diff --git a/cmd/sigsum/test/signify.sh b/cmd/sigsum/test/signify.sh new file mode 100755 index 0000000..8e86e8d --- /dev/null +++ b/cmd/sigsum/test/signify.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +set -e +trap cleanup EXIT + +pass=1234 +priv=keys/signify.sec +pub=keys/signify.pub +domain_hint=_sigsum_v0.test-only.rgdd.se +msg=msg-$(date +%s) +num_msg=3 + +function cleanup() { + set +e + + rm -f sigsum + for i in $(seq 1 $num_msg); do + rm -f $msg-$i{,.trunnel,.sig} + done + + exit +} + +go build ../ + +files="" +for i in $(seq 1 $num_msg); do + echo $msg-$i > $msg-$i + if ! ./sigsum format $msg-$i > $msg-$i.trunnel; then + echo "[FAIL] format for $num_msg signify message(s)" >&2 + exit 1 + fi + if ! echo $pass | signify-openbsd -Ss $priv -m $msg-$i.trunnel -x $msg-$i.sig; then + echo "[FAIL] sign for $num_msg signify message(s)" >&2 + exit 1 + fi + files=$(echo -n $files $msg-$i) +done + +echo "[PASS] format for $num_msg signify message(s)" >&2 +echo "[PASS] sign for $num_msg signify message(s)" >&2 + +if ! ./sigsum bundle -t signify -k $pub -d $domain_hint $files; then + echo "[FAIL] bundle for $num_msg signify message(s)" >&2 + exit 1 +fi + +echo "[PASS] bundle for $num_msg signify message(s)" >&2 + +if ! ./sigsum verify -t signify -k $pub $files; then + echo "[FAIL] verify for $num_msg signify message(s)" >&2 + exit 1 +fi + +echo "[PASS] verify for $num_msg signify message(s)" >&2 @@ -0,0 +1,5 @@ +module git.sigsum.org/sigsum-tool-go + +go 1.15 + +require git.sigsum.org/sigsum-lib-go v0.0.2 @@ -0,0 +1,2 @@ +git.sigsum.org/sigsum-lib-go v0.0.2 h1:1FwdnraPaasw1D1Lb+flRMJRGLTuZrp17AZ6tx+iT/0= +git.sigsum.org/sigsum-lib-go v0.0.2/go.mod h1:DVmlcf0MBHy4IZdnZ5DcbsKkGEd0EkOAoLINhLgcndY= 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") +} diff --git a/pkg/policy/default.go b/pkg/policy/default.go new file mode 100644 index 0000000..6f35fa4 --- /dev/null +++ b/pkg/policy/default.go @@ -0,0 +1,66 @@ +package policy + +import ( + "fmt" + + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type DefaultPolicy struct{} + +// Verify checks if the cosigned tree head satisifies the policy "one log and +// a majority of witnesses". Any unverifiable cosignatures are removed. +func (dp *DefaultPolicy) Verify(*types.CosignedTreeHead) error { + return fmt.Errorf("TODO") +} + +func (dp *DefaultPolicy) Logs() []Log { + return []Log{ + Log{ + Name: "Test log operated by ln5", + LogURL: "https://poc.sigsum.org/glasswing-butterfly/sigsum/v0", + PublicKey: types.PublicKey{ + 74, 179, 210, 181, + 30, 71, 220, 157, + 228, 204, 228, 189, + 235, 169, 224, 134, + 34, 233, 58, 47, + 127, 137, 180, 203, + 119, 25, 127, 198, + 176, 4, 77, 231, + }, + ShardStart: 1639579652, + TrustUntil: 1648771200, // Apr 1 2022 + }, + } +} + +func (dp *DefaultPolicy) Witnesses() []Witness { + return []Witness{ + Witness{ + Name: "Test witness operated by rgdd", + PublicKey: types.PublicKey{ + 129, 45, 190, 240, + 21, 107, 7, 158, + 45, 4, 135, 71, + 178, 24, 156, 191, + 166, 79, 150, 226, + 32, 74, 23, 203, + 35, 203, 82, 128, + 128, 135, 21, 3, + }, + TrustUntil: 1648771200, // Apr 1 2022 + }, + } +} + +// ShardHint returns the smallest shard hint that all logs accept +func (dp *DefaultPolicy) ShardHint() uint64 { + var shardHint uint64 + for _, l := range dp.Logs() { + if l.ShardStart > shardHint { + shardHint = l.ShardStart + } + } + return shardHint +} diff --git a/pkg/policy/policy.go b/pkg/policy/policy.go new file mode 100644 index 0000000..b4fe920 --- /dev/null +++ b/pkg/policy/policy.go @@ -0,0 +1,32 @@ +package policy + +import ( + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type Policy interface { + // Logs returns the list of known logs. It is needed so that an + // appropriate log can be picked when submitting a signed checksum. + Logs() []Log + + // ShardHint returns a recommended shard hint. + ShardHint() uint64 + + // Verify checks if a cosigned tree head is trustworthy. On success, + // any invalid or unknown cosignatures may be removed. + Verify(*types.CosignedTreeHead) error +} + +type Log struct { + Name string + LogURL string + PublicKey types.PublicKey + ShardStart uint64 + TrustUntil uint64 +} + +type Witness struct { + Name string + PublicKey types.PublicKey + TrustUntil uint64 +} diff --git a/pkg/signatures/minisign/minisign.go b/pkg/signatures/minisign/minisign.go new file mode 100644 index 0000000..8d8a9c4 --- /dev/null +++ b/pkg/signatures/minisign/minisign.go @@ -0,0 +1,22 @@ +package minisign + +import ( + "fmt" + "io" + + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type Parser struct{} + +func (p *Parser) SignatureSuffix() string { + return ".minisig" +} + +func (p *Parser) PublicKey(r io.Reader) (*types.PublicKey, error) { + return nil, fmt.Errorf("TODO") +} + +func (p *Parser) Signature(r io.Reader) (*types.Signature, error) { + return nil, fmt.Errorf("TODO") +} diff --git a/pkg/signatures/signatures.go b/pkg/signatures/signatures.go new file mode 100644 index 0000000..184c8f3 --- /dev/null +++ b/pkg/signatures/signatures.go @@ -0,0 +1,13 @@ +package signatures + +import ( + "io" + + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type Parser interface { + PublicKey(io.Reader) (*types.PublicKey, error) + Signature(io.Reader) (*types.Signature, error) + SignatureSuffix() string +} diff --git a/pkg/signatures/signify/signify.go b/pkg/signatures/signify/signify.go new file mode 100644 index 0000000..6dbbf07 --- /dev/null +++ b/pkg/signatures/signify/signify.go @@ -0,0 +1,91 @@ +package signify + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type Parser struct{} + +func (p *Parser) SignatureSuffix() string { + return ".sig" +} + +func (p *Parser) PublicKey(r io.Reader) (*types.PublicKey, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("signify: read failed: %v", err) + } + var pub types.PublicKey + if err := parse(pub[:], b); err != nil { + return nil, fmt.Errorf("signify: %v", err) + } + return &pub, nil +} + +func (p *Parser) Signature(r io.Reader) (*types.Signature, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("signify: read failed: %v", err) + } + var sig types.Signature + if err := parse(sig[:], b); err != nil { + return nil, fmt.Errorf("signify: %v", err) + } + return &sig, nil +} + +func parse(dst, lines []byte) error { + commentLine, dataLine, err := parseLines(lines) + if err != nil { + return fmt.Errorf("invalid: %v", err) + } + if err := parseCommentLine(commentLine); err != nil { + return fmt.Errorf("invalid: %v", err) + } + if err := parseDataLine(dst, dataLine); err != nil { + return fmt.Errorf("invalid: %v", err) + } + return nil +} + +func parseLines(lines []byte) ([]byte, []byte, error) { + split := bytes.Split(lines, []byte("\n")) + if len(split) != 3 { + return nil, nil, fmt.Errorf("number of lines") + } + return split[0], split[1], nil +} + +func parseCommentLine(line []byte) error { + if !bytes.HasPrefix(line, []byte("untrusted comment: ")) { + return fmt.Errorf("no untrusted comment") + } + return nil +} + +func parseDataLine(dst, line []byte) error { + data, err := base64.StdEncoding.DecodeString(string(line)) + if err != nil { + return fmt.Errorf("base64 encoding") + } + if len(data) < 2 || !bytes.Equal(data[:2], []byte("Ed")) { + return fmt.Errorf("algorithm") + } + data = data[2:] + if len(data) < 8 { + return fmt.Errorf("random fingerprint") + } + data = data[8:] + if len(data) != len(dst) { + return fmt.Errorf("data length") + } + + copy(dst[:], data) + return nil +} diff --git a/pkg/signatures/ssh/ssh.go b/pkg/signatures/ssh/ssh.go new file mode 100644 index 0000000..d5fb8f4 --- /dev/null +++ b/pkg/signatures/ssh/ssh.go @@ -0,0 +1,22 @@ +package ssh + +import ( + "fmt" + "io" + + "git.sigsum.org/sigsum-lib-go/pkg/types" +) + +type Parser struct{} + +func (p *Parser) SignatureSuffix() string { + return ".sig" +} + +func (p *Parser) PublicKey(r io.Reader) (*types.PublicKey, error) { + return nil, fmt.Errorf("TODO") +} + +func (p *Parser) Signature(r io.Reader) (*types.Signature, error) { + return nil, fmt.Errorf("TODO") +} |