From 6e19e1882bc74183374dd297771531f2f2b3a4a0 Mon Sep 17 00:00:00 2001 From: Zachary Newman Date: Tue, 1 Feb 2022 10:57:03 -0500 Subject: [PATCH] Add `payload` and `add-signature` commands. Fixes #205. --- cmd/tuf/add_signature.go | 37 +++++++++++++++++++++++++++++++++++++ cmd/tuf/main.go | 2 ++ cmd/tuf/payload.go | 27 +++++++++++++++++++++++++++ repo.go | 16 ++++++++++++++++ repo_test.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+) create mode 100644 cmd/tuf/add_signature.go create mode 100644 cmd/tuf/payload.go diff --git a/cmd/tuf/add_signature.go b/cmd/tuf/add_signature.go new file mode 100644 index 000000000..5dbbe19fc --- /dev/null +++ b/cmd/tuf/add_signature.go @@ -0,0 +1,37 @@ +package main + +import ( + "os" + + "github.com/flynn/go-docopt" + "github.com/theupdateframework/go-tuf" + "github.com/theupdateframework/go-tuf/data" +) + +func init() { + register("add-signature", cmdAddSignature, ` +usage: tuf add-signature --key-id --signature + +Adds a signature (as hex-encoded bytes) generated by an offline tool to the given role. + +If the signature does not verify, it will not be added. +`) +} + +func cmdAddSignature(args *docopt.Args, repo *tuf.Repo) error { + role := args.String[""] + keyID := args.String[""] + + f := args.String[""] + sigBytes, err := os.ReadFile(f) + if err != nil { + return err + } + sigData := data.HexBytes(sigBytes) + + sig := data.Signature{ + KeyID: keyID, + Signature: sigData, + } + return repo.AddOrUpdateSignature(role, sig) +} diff --git a/cmd/tuf/main.go b/cmd/tuf/main.go index 4017987e2..8738ae4cc 100644 --- a/cmd/tuf/main.go +++ b/cmd/tuf/main.go @@ -36,6 +36,8 @@ Commands: remove Remove a target file snapshot Update the snapshot metadata file timestamp Update the timestamp metadata file + payload Output a role's metadata file for signing + add-signature Adds a signature generated offline sign Sign a role's metadata file commit Commit staged files to the repository regenerate Recreate the targets metadata file [Not supported yet] diff --git a/cmd/tuf/payload.go b/cmd/tuf/payload.go new file mode 100644 index 000000000..ebb5bcff6 --- /dev/null +++ b/cmd/tuf/payload.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "github.com/flynn/go-docopt" + "github.com/theupdateframework/go-tuf" +) + +func init() { + register("payload", cmdPayload, ` +usage: tuf payload + +Output a role's metadata in a ready-to-sign format. + +The output is canonicalized. +`) +} + +func cmdPayload(args *docopt.Args, repo *tuf.Repo) error { + p, err := repo.Payload(args.String[""]) + if err != nil { + return err + } + fmt.Print(string(p)) + return nil +} diff --git a/repo.go b/repo.go index 482cdf00d..d906c55f4 100644 --- a/repo.go +++ b/repo.go @@ -1092,3 +1092,19 @@ func (r *Repo) timestampFileMeta(roleFilename string) (data.TimestampFileMeta, e } return util.GenerateTimestampFileMeta(bytes.NewReader(b), r.hashAlgorithms...) } + +func (r *Repo) Payload(roleFilename string) ([]byte, error) { + // TODO: if you pass "root" instead of "root.json" you get a cryptic error message + // this is probably true for "sign" as well + role := strings.TrimSuffix(roleFilename, ".json") + if !roles.IsTopLevelRole(role) { + return nil, ErrInvalidRole{role} + } + + s, err := r.SignedMeta(roleFilename) + if err != nil { + return nil, err + } + + return s.Signed, nil +} diff --git a/repo_test.go b/repo_test.go index 1e46147c5..cdb405e86 100644 --- a/repo_test.go +++ b/repo_test.go @@ -1830,3 +1830,32 @@ func (rs *RepoSuite) TestSignDigest(c *C) { c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes) } + +func (rs *RepoSuite) TestPayload(c *C) { + signer, err := keys.GenerateEd25519Key() + c.Assert(err, IsNil) + + // meta := map[string]json.RawMessage{"root.json": []byte(`{"signed":{},"signatures":[]}`)} + meta := make(map[string]json.RawMessage) + local := MemoryStore(meta, nil) + r, err := NewRepo(local) + c.Assert(err, IsNil) + c.Assert(r.Init(false), IsNil) + + err = r.AddVerificationKey("root", signer.PublicData()) + c.Assert(err, IsNil) + + payload, err := r.Payload("root.json") + c.Assert(err, IsNil) + rawSig, err := signer.SignMessage(payload) + keyID := signer.PublicData().IDs()[0] + sig := data.Signature{ + KeyID: keyID, + Signature: rawSig, + } + c.Assert(err, IsNil) + + // This method checks that the signature verifies! + err = r.AddOrUpdateSignature("root.json", sig) + c.Assert(err, IsNil) +}