diff --git a/client/client.go b/client/client.go index 0459a1d7..1ecff4df 100644 --- a/client/client.go +++ b/client/client.go @@ -2,6 +2,7 @@ package client import ( "bytes" + "encoding/hex" "encoding/json" "io" "io/ioutil" @@ -841,6 +842,29 @@ func (c *Client) Download(name string, dest Destination) (err error) { return nil } +func (c *Client) VerifyDigest(digest string, digestAlg string, length int64, path string) error { + localMeta, ok := c.targets[path] + if !ok { + return ErrUnknownTarget{Name: path, SnapshotVersion: c.snapshotVer} + } + + actual := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)} + var err error + actual.Hashes[digestAlg], err = hex.DecodeString(digest) + if err != nil { + return err + } + + if err := util.TargetFileMetaEqual(data.TargetFileMeta{FileMeta: actual}, localMeta); err != nil { + if e, ok := err.(util.ErrWrongLength); ok { + return ErrWrongSize{path, e.Actual, e.Expected} + } + return ErrDownloadFailed{path, err} + } + + return nil +} + // Target returns the target metadata for a specific target if it // exists, searching from top-level level targets then through // all delegations. If it does not, ErrNotFound will be returned. diff --git a/client/client_test.go b/client/client_test.go index 5cbaf44e..c0be2443 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1178,3 +1178,21 @@ func generateRepoFS(c *C, dir string, files map[string][]byte, consistentSnapsho c.Assert(repo.Commit(), IsNil) return repo } + +func (s *ClientSuite) TestVerifyDigest(c *C) { + digest := "sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c" + hash := "bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c" + size := int64(42) + + c.Assert(s.repo.AddTargetsWithDigest(hash, "sha256", size, digest, nil), IsNil) + c.Assert(s.repo.Snapshot(), IsNil) + c.Assert(s.repo.Timestamp(), IsNil) + c.Assert(s.repo.Commit(), IsNil) + s.syncRemote(c) + + client := s.newClient(c) + _, err := client.Update() + c.Assert(err, IsNil) + + c.Assert(client.VerifyDigest(hash, "sha256", size, digest), IsNil) +} diff --git a/repo.go b/repo.go index e4cec169..60554101 100644 --- a/repo.go +++ b/repo.go @@ -2,6 +2,7 @@ package tuf import ( "bytes" + "encoding/hex" "encoding/json" "fmt" "io" @@ -703,6 +704,34 @@ func (r *Repo) AddTargets(paths []string, custom json.RawMessage) error { return r.AddTargetsWithExpires(paths, custom, data.DefaultExpires("targets")) } +func (r *Repo) AddTargetsWithDigest(digest string, digestAlg string, length int64, path string, custom json.RawMessage) error { + expires := data.DefaultExpires("targets") + + // TODO: support delegated targets + t, err := r.topLevelTargets() + if err != nil { + return err + } + + meta := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)} + meta.Hashes[digestAlg], err = hex.DecodeString(digest) + if err != nil { + return err + } + + // If custom is provided, set custom, otherwise maintain existing custom + // metadata + if len(custom) > 0 { + meta.Custom = &custom + } else if t, ok := t.Targets[path]; ok { + meta.Custom = t.Custom + } + + t.Targets[path] = data.TargetFileMeta{FileMeta: meta} + + return r.writeTargetWithExpires(t, expires) +} + func (r *Repo) AddTargetWithExpires(path string, custom json.RawMessage, expires time.Time) error { return r.AddTargetsWithExpires([]string{path}, custom, expires) } @@ -742,12 +771,16 @@ func (r *Repo) AddTargetsWithExpires(paths []string, custom json.RawMessage, exp }); err != nil { return err } + return r.writeTargetWithExpires(t, expires) +} + +func (r *Repo) writeTargetWithExpires(t *data.Targets, expires time.Time) error { t.Expires = expires.Round(time.Second) if !r.local.FileIsStaged("targets.json") { t.Version++ } - err = r.setTopLevelMeta("targets.json", t) + err := r.setTopLevelMeta("targets.json", t) if err == nil { fmt.Println("Added/staged targets:") for k := range t.Targets { diff --git a/repo_test.go b/repo_test.go index f378aa89..fbecd613 100644 --- a/repo_test.go +++ b/repo_test.go @@ -3,6 +3,7 @@ package tuf import ( "crypto" "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -1795,3 +1796,34 @@ func (rs *RepoSuite) TestBadAddOrUpdateSignatures(c *C) { } checkSigIDs("root.json") } + +func (rs *RepoSuite) TestSignDigest(c *C) { + files := map[string][]byte{"foo.txt": []byte("foo")} + local := MemoryStore(make(map[string]json.RawMessage), files) + r, err := NewRepo(local) + c.Assert(err, IsNil) + + genKey(c, r, "root") + genKey(c, r, "targets") + genKey(c, r, "snapshot") + genKey(c, r, "timestamp") + + digest := "sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c" + hash := "bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c" + size := int64(42) + + c.Assert(r.AddTargetsWithDigest(hash, "sha256", size, digest, nil), IsNil) + c.Assert(r.Snapshot(), IsNil) + c.Assert(r.Timestamp(), IsNil) + c.Assert(r.Commit(), IsNil) + + digest_bytes, err := hex.DecodeString("bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c") + hex_digest_bytes := data.HexBytes(digest_bytes) + c.Assert(err, IsNil) + + targets, err := r.topLevelTargets() + c.Assert(err, IsNil) + c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Length, Equals, size) + c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes) + +}