diff --git a/tuf/data/types.go b/tuf/data/types.go index 6b61f4e192..29acb3ea29 100644 --- a/tuf/data/types.go +++ b/tuf/data/types.go @@ -161,6 +161,44 @@ func CheckHashes(payload []byte, name string, hashes Hashes) error { return nil } +// CompareMultiHashes verifies that the two Hashes passed in can represent the same data. +// This means that both maps must have at least one key defined for which they map, and no conflicts. +// Note that we only check supported hashes, like in CheckHashes +func CompareMultiHashes(hashes1, hashes2 Hashes) error { + // First check if the two hash structures are valid + if err := CheckValidHashStructures(hashes1); err != nil { + return err + } + if err := CheckValidHashStructures(hashes2); err != nil { + return err + } + // Check if they have at least one matching hash, and no conflicts + cnt := 0 + for _, hashAlg := range NotaryDefaultHashes { + hash1, ok := hashes1[hashAlg] + if !ok { + continue + } + + hash2, ok := hashes2[hashAlg] + if !ok { + continue + } + + if subtle.ConstantTimeCompare(hash1[:], hash2[:]) == 0 { + return fmt.Errorf("mismatched %s checksum", hashAlg) + } + // If we reached here, we had a match + cnt++ + } + + if cnt == 0 { + return fmt.Errorf("at least one supported and matching hash needed") + } + + return nil +} + // CheckValidHashStructures returns an error, or nil, depending on whether // the content of the hashes is valid or not. func CheckValidHashStructures(hashes Hashes) error { diff --git a/tuf/data/types_test.go b/tuf/data/types_test.go index 5bb980bc80..8f2dd9e265 100644 --- a/tuf/data/types_test.go +++ b/tuf/data/types_test.go @@ -3,6 +3,7 @@ package data import ( "bytes" "encoding/hex" + "strings" "testing" "github.com/docker/go/canonical/json" @@ -176,3 +177,52 @@ func TestCheckValidHashStructures(t *testing.T) { err = CheckValidHashStructures(hashes) require.IsType(t, ErrInvalidChecksum{}, err) } + +func TestCompareMultiHashes(t *testing.T) { + var err error + hashes1 := make(Hashes) + hashes2 := make(Hashes) + + // Expected to fail since there are no checksums at all + err = CompareMultiHashes(hashes1, hashes2) + require.Error(t, err) + + // Expected to fail even though the checksum of sha384 is valid, + // because we haven't provided a supported hash algorithm yet (ex: sha256). + hashes1["sha384"], err = hex.DecodeString("64becc3c23843942b1040ffd4743d1368d988ddf046d17d448a6e199c02c3044b425a680112b399d4dbe9b35b7ccc989") + hashes2["sha384"], err = hex.DecodeString("64becc3c23843942b1040ffd4743d1368d988ddf046d17d448a6e199c02c3044b425a680112b399d4dbe9b35b7ccc989") + err = CompareMultiHashes(hashes1, hashes2) + require.Error(t, err) + + // Now both have a matching sha256, so this will pass + hashes1["sha256"], err = hex.DecodeString("766af0ef090a4f2307e49160fa242db6fb95f071ad81a198eeb7d770e61cd6d8") + hashes2["sha256"], err = hex.DecodeString("766af0ef090a4f2307e49160fa242db6fb95f071ad81a198eeb7d770e61cd6d8") + err = CompareMultiHashes(hashes1, hashes2) + require.NoError(t, err) + + // Because the sha384 algorithm isn't supported, it's treated as an extra key that won't be checked + // So even if it doesn't match, that's fine as long as all supported keys match + hashes2["sha384"], err = hex.DecodeString(strings.Repeat("a", 96)) + err = CompareMultiHashes(hashes1, hashes2) + require.NoError(t, err) + + // only add a sha512 to hashes1, but comparison will still succeed because there's no mismatch and we have the sha256 match + hashes1["sha512"], err = hex.DecodeString("795d9e95db099464b6730844f28effddb010b0d5abae5d5892a6ee04deacb09c9e622f89e816458b5a1a81761278d7d3a6a7c269d9707eff8858b16c51de0315") + err = CompareMultiHashes(hashes1, hashes2) + require.NoError(t, err) + + // remove sha256 from hashes1, comparison will fail now because there are no matches + delete(hashes1, "sha256") + err = CompareMultiHashes(hashes1, hashes2) + require.Error(t, err) + + // add sha512 to hashes2, comparison will now pass because both have matching sha512s + hashes2["sha512"], err = hex.DecodeString("795d9e95db099464b6730844f28effddb010b0d5abae5d5892a6ee04deacb09c9e622f89e816458b5a1a81761278d7d3a6a7c269d9707eff8858b16c51de0315") + err = CompareMultiHashes(hashes1, hashes2) + require.NoError(t, err) + + // change the sha512 for hashes2, comparison will now fail + hashes2["sha512"], err = hex.DecodeString(strings.Repeat("a", notary.Sha512HexSize)) + err = CompareMultiHashes(hashes1, hashes2) + require.Error(t, err) +}