Skip to content

Commit e3efe98

Browse files
authored
fix: verify length and hashes of fetched bytes before parsing (#325)
* feat: add func to util for comparing raw bytes Add BytesMatchLenAndHashes() to verify that fetched bytes match the expected length and hashes. This is useful to check data before parsing. Signed-off-by: Joshua Lock <[email protected]> * fix: verify snapshot's len and hashes before parsing Signed-off-by: Joshua Lock <[email protected]> * fix: verify targets' len and hashes before parsing Signed-off-by: Joshua Lock <[email protected]> * feat: export version comparison utility function Signed-off-by: Joshua Lock <[email protected]> * fix: only check version after parsing fetched ss and ts We check the length and hashes of the fetched bytes before parsing them, therefore once the data are parsed into a FileMeta we only need to check against the expected version. The length and hashes checks are no longer required at this point, as they have already been done. Signed-off-by: Joshua Lock <[email protected]> * fix: remove spurious length check Signed-off-by: Joshua Lock <[email protected]> * chore: test newly exported util functions Add tests for newly exported functions in the util package: * VersionEqual() - simple test of version number comparison * BytesMatchLenAndHashes() - check function returns appropriate errors when incorrect length, hashes, or both are passed Signed-off-by: Joshua Lock <[email protected]>
1 parent 439ce47 commit e3efe98

File tree

3 files changed

+125
-7
lines changed

3 files changed

+125
-7
lines changed

client/client.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,14 +674,21 @@ func (c *Client) downloadMetaFromSnapshot(name string, m data.SnapshotFileMeta)
674674
return nil, err
675675
}
676676

677+
// 5.6.2 – Check length and hashes of fetched bytes *before* parsing metadata
678+
if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
679+
return nil, ErrDownloadFailed{name, err}
680+
}
681+
677682
meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.HashAlgorithms()...)
678683
if err != nil {
679684
return nil, err
680685
}
681-
// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
682-
if err := util.SnapshotFileMetaEqual(meta, m); err != nil {
686+
687+
// 5.6.4 - Check against snapshot role's version
688+
if err := util.VersionEqual(meta.Version, m.Version); err != nil {
683689
return nil, ErrDownloadFailed{name, err}
684690
}
691+
685692
return b, nil
686693
}
687694

@@ -691,14 +698,21 @@ func (c *Client) downloadMetaFromTimestamp(name string, m data.TimestampFileMeta
691698
return nil, err
692699
}
693700

701+
// 5.2.2. – Check length and hashes of fetched bytes *before* parsing metadata
702+
if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
703+
return nil, ErrDownloadFailed{name, err}
704+
}
705+
694706
meta, err := util.GenerateTimestampFileMeta(bytes.NewReader(b), m.HashAlgorithms()...)
695707
if err != nil {
696708
return nil, err
697709
}
698-
// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
699-
if err := util.TimestampFileMetaEqual(meta, m); err != nil {
710+
711+
// 5.5.4 - Check against timestamp role's version
712+
if err := util.VersionEqual(meta.Version, m.Version); err != nil {
700713
return nil, ErrDownloadFailed{name, err}
701714
}
715+
702716
return b, nil
703717
}
704718

util/util.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,32 @@ func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error {
8686
return nil
8787
}
8888

89+
func BytesMatchLenAndHashes(fetched []byte, length int64, hashes data.Hashes) error {
90+
flen := int64(len(fetched))
91+
if length != 0 && flen != length {
92+
return ErrWrongLength{length, flen}
93+
}
94+
95+
for alg, expected := range hashes {
96+
var h hash.Hash
97+
switch alg {
98+
case "sha256":
99+
h = sha256.New()
100+
case "sha512":
101+
h = sha512.New()
102+
default:
103+
return ErrUnknownHashAlgorithm{alg}
104+
}
105+
h.Write(fetched)
106+
hash := h.Sum(nil)
107+
if !hmac.Equal(hash, expected) {
108+
return ErrWrongHash{alg, expected, hash}
109+
}
110+
}
111+
112+
return nil
113+
}
114+
89115
func hashEqual(actual data.Hashes, expected data.Hashes) error {
90116
hashChecked := false
91117
for typ, hash := range expected {
@@ -102,7 +128,7 @@ func hashEqual(actual data.Hashes, expected data.Hashes) error {
102128
return nil
103129
}
104130

105-
func versionEqual(actual int64, expected int64) error {
131+
func VersionEqual(actual int64, expected int64) error {
106132
if actual != expected {
107133
return ErrWrongVersion{expected, actual}
108134
}
@@ -125,7 +151,7 @@ func SnapshotFileMetaEqual(actual data.SnapshotFileMeta, expected data.SnapshotF
125151
}
126152
}
127153
// 5.6.4 - Check against snapshot role's snapshot version
128-
if err := versionEqual(actual.Version, expected.Version); err != nil {
154+
if err := VersionEqual(actual.Version, expected.Version); err != nil {
129155
return err
130156
}
131157

@@ -149,7 +175,7 @@ func TimestampFileMetaEqual(actual data.TimestampFileMeta, expected data.Timesta
149175
}
150176
}
151177
// 5.5.4 - Check against timestamp role's snapshot version
152-
if err := versionEqual(actual.Version, expected.Version); err != nil {
178+
if err := VersionEqual(actual.Version, expected.Version); err != nil {
153179
return err
154180
}
155181

util/util_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package util
22

33
import (
44
"bytes"
5+
"crypto/sha256"
6+
"crypto/sha512"
57
"encoding/hex"
8+
"hash"
69
"testing"
710

811
"github.com/theupdateframework/go-tuf/data"
@@ -195,3 +198,78 @@ func (UtilSuite) TestHashedPaths(c *C) {
195198
delete(expected, path)
196199
}
197200
}
201+
202+
func (UtilSuite) TestVersionEqual(c *C) {
203+
c.Assert(VersionEqual(1, 1), IsNil)
204+
c.Assert(VersionEqual(1, 3), Equals, ErrWrongVersion{3, 1})
205+
}
206+
207+
func makeHash(b []byte, alg string) []byte {
208+
var h hash.Hash
209+
210+
switch alg {
211+
case "sha256":
212+
h = sha256.New()
213+
case "sha512":
214+
h = sha512.New()
215+
}
216+
h.Write(b)
217+
return h.Sum(nil)
218+
}
219+
220+
func (UtilSuite) TestBytesMatchLenAndHashes(c *C) {
221+
type test struct {
222+
name string
223+
bytes []byte
224+
length int64
225+
hashes data.Hashes
226+
err func(test) error
227+
}
228+
229+
b := []byte{82, 253, 252, 7, 33, 130, 101, 79, 22, 63, 95, 15, 154, 98, 29, 114}
230+
bhashes := data.Hashes{
231+
"sha512": makeHash(b, "sha512"),
232+
"sha256": makeHash(b, "sha256"),
233+
}
234+
235+
tests := []test{
236+
{
237+
name: "correct len and hashes",
238+
bytes: b,
239+
length: 16,
240+
hashes: bhashes,
241+
err: func(test) error { return nil },
242+
},
243+
{
244+
name: "incorrect len",
245+
bytes: b,
246+
length: 32,
247+
hashes: bhashes,
248+
err: func(test) error { return ErrWrongLength{32, 16} },
249+
},
250+
{
251+
name: "incorrect hashes",
252+
bytes: b,
253+
length: 16,
254+
hashes: data.Hashes{
255+
"sha512": makeHash(b, "sha256"),
256+
"sha256": makeHash(b, "sha512"),
257+
},
258+
err: func(test) error { return ErrWrongHash{"sha512", bhashes["sha256"], bhashes["sha512"]} },
259+
},
260+
{
261+
name: "incorrect len and hashes",
262+
bytes: b,
263+
length: 32,
264+
hashes: data.Hashes{
265+
"sha512": makeHash(b, "sha256"),
266+
"sha256": makeHash(b, "sha512"),
267+
},
268+
err: func(test) error { return ErrWrongLength{32, 16} },
269+
},
270+
}
271+
272+
for _, t := range tests {
273+
c.Assert(BytesMatchLenAndHashes(t.bytes, t.length, t.hashes), DeepEquals, t.err(t), Commentf("name = %s", t.name))
274+
}
275+
}

0 commit comments

Comments
 (0)