blob: 47fdc229b415c9238895c48754bb097c3fb9a4b0 [file] [log] [blame]
package client
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/theupdateframework/go-tuf/data"
"github.com/theupdateframework/go-tuf/util"
"github.com/theupdateframework/go-tuf/verify"
)
func TestGetTargetMeta(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer func() { assert.Nil(t, closer()) }()
_, err := c.Update()
assert.Nil(t, err)
f, err := c.getTargetFileMeta("f.txt")
assert.Nil(t, err)
hash := sha256.Sum256([]byte("Contents: f.txt"))
assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
f, err = c.getTargetFileMeta("targets.txt")
assert.Nil(t, err)
hash = sha256.Sum256([]byte("Contents: targets.txt"))
assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
}
func TestMaxDelegations(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer func() { assert.Nil(t, closer()) }()
_, err := c.Update()
assert.Nil(t, err)
c.MaxDelegations = 2
_, err = c.getTargetFileMeta("c.txt")
assert.Equal(t, ErrMaxDelegations{Target: "c.txt", MaxDelegations: 2, SnapshotVersion: 2}, err)
}
func TestMetaNotFound(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer func() { assert.Nil(t, closer()) }()
_, err := c.Update()
assert.Nil(t, err)
_, err = c.getTargetFileMeta("unknown.txt")
assert.Equal(t, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2}, err)
}
type fakeRemote struct {
getMeta func(name string) (stream io.ReadCloser, size int64, err error)
getTarget func(path string) (stream io.ReadCloser, size int64, err error)
}
func (f fakeRemote) GetMeta(name string) (stream io.ReadCloser, size int64, err error) {
return f.getMeta(name)
}
func (f fakeRemote) GetTarget(name string) (stream io.ReadCloser, size int64, err error) {
return f.getTarget(name)
}
func TestTargetsNotFound(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer func() { assert.Nil(t, closer()) }()
_, err := c.Update()
assert.Nil(t, err)
previousRemote := c.remote
newRemote := fakeRemote{
getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
if path == "1.c.json" {
return nil, 0, ErrNotFound{}
}
return previousRemote.GetMeta(path)
},
getTarget: previousRemote.GetTarget,
}
c.remote = newRemote
_, err = c.getTargetFileMeta("c.txt")
assert.Equal(t, ErrMissingRemoteMetadata{Name: "c.json"}, err)
}
func TestUnverifiedTargets(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer closer()
_, err := c.Update()
assert.Nil(t, err)
previousRemote := c.remote
newRemote := fakeRemote{
getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
if path == "1.c.json" {
// returns a snapshot that does not match
return previousRemote.GetMeta("1.d.json")
}
return previousRemote.GetMeta(path)
},
getTarget: previousRemote.GetTarget,
}
c.remote = newRemote
_, err = c.getTargetFileMeta("c.txt")
assert.Equal(t, ErrDecodeFailed{File: "c.json", Err: verify.ErrRoleThreshold{Expected: 1, Actual: 0}}, err)
}
func TestPersistedMeta(t *testing.T) {
verify.IsExpired = func(t time.Time) bool { return false }
c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
defer closer()
_, err := c.Update()
assert.Nil(t, err)
_, err = c.local.GetMeta()
assert.Nil(t, err)
type expectedTargets struct {
name string
version int64
}
var persistedTests = []struct {
file string
targets []expectedTargets
downloadError error
targetError error
fileContent string
}{
{
file: "unknown",
targets: []expectedTargets{
{
name: "targets.json",
version: 2,
},
},
downloadError: ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
targetError: ErrNotFound{File: "unknown"},
fileContent: "",
},
{
file: "b.txt",
targets: []expectedTargets{
{
name: "targets.json",
version: 2,
},
{
name: "a.json",
version: 1,
},
{
name: "b.json",
version: 1,
},
},
downloadError: nil,
targetError: nil,
fileContent: "Contents: b.txt",
},
{
file: "f.txt",
targets: []expectedTargets{
{
name: "targets.json",
version: 2,
},
{
name: "a.json",
version: 1,
},
{
name: "b.json",
version: 1,
},
{
name: "c.json",
version: 1,
},
{
name: "d.json",
version: 1,
},
{
name: "e.json",
version: 1,
},
{
name: "f.json",
version: 1,
},
},
downloadError: nil,
targetError: nil,
fileContent: "Contents: f.txt",
},
}
for _, tt := range persistedTests {
t.Run("search "+tt.file, func(t *testing.T) {
var dest testDestination
err = c.Download(tt.file, &dest)
assert.Equal(t, tt.downloadError, err)
assert.Equal(t, tt.fileContent, dest.String())
target, err := c.Target(tt.file)
assert.Equal(t, tt.targetError, err)
if tt.targetError == nil {
meta, err := util.GenerateTargetFileMeta(strings.NewReader(tt.fileContent), target.HashAlgorithms()...)
assert.Nil(t, err)
assert.Nil(t, util.TargetFileMetaEqual(target, meta))
}
p, err := c.local.GetMeta()
assert.Nil(t, err)
persisted := copyStore(p)
// trim non targets metas
for _, notTargets := range []string{"root.json", "snapshot.json", "timestamp.json"} {
delete(persisted, notTargets)
}
for _, targets := range tt.targets {
storedVersion, err := versionOfStoredTargets(targets.name, persisted)
assert.Equal(t, targets.version, storedVersion)
assert.Nil(t, err)
delete(persisted, targets.name)
}
assert.Empty(t, persisted)
})
}
}
func versionOfStoredTargets(name string, store map[string]json.RawMessage) (int64, error) {
rawTargets, ok := store[name]
if !ok {
return 0, nil
}
s := &data.Signed{}
if err := json.Unmarshal(rawTargets, s); err != nil {
return 0, err
}
targets := &data.Targets{}
if err := json.Unmarshal(s.Signed, targets); err != nil {
return 0, err
}
return targets.Version, nil
}
func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() error) {
serverDir := dirPrefix + "/server"
initialStateDir := dirPrefix + "/client/metadata/current"
l, err := net.Listen("tcp", "127.0.0.1:0")
assert.Nil(t, err)
addr := l.Addr().String()
go http.Serve(l, http.FileServer(http.Dir(serverDir)))
opts := &HTTPRemoteOptions{
MetadataPath: "metadata",
TargetsPath: "targets",
}
remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/", addr), opts, nil)
assert.Nil(t, err)
c := NewClient(MemoryLocalStore(), remote)
rawFile, err := os.ReadFile(initialStateDir + "/" + "root.json")
assert.Nil(t, err)
assert.Nil(t, c.Init(rawFile))
files, err := os.ReadDir(initialStateDir)
assert.Nil(t, err)
// load local files
for _, f := range files {
if f.IsDir() {
continue
}
name := f.Name()
// ignoring consistent snapshot when loading initial state
if len(strings.Split(name, ".")) == 1 && strings.HasSuffix(name, ".json") {
rawFile, err := os.ReadFile(initialStateDir + "/" + name)
assert.Nil(t, err)
assert.Nil(t, c.local.SetMeta(name, rawFile))
}
}
return c, l.Close
}
func copyStore(store map[string]json.RawMessage) map[string]json.RawMessage {
new := make(map[string]json.RawMessage, len(store))
for k, raw := range store {
newRaw := make([]byte, len(raw))
copy(newRaw, []byte(raw))
new[k] = json.RawMessage(newRaw)
}
return new
}