...

Source file src/github.com/theupdateframework/go-tuf/client/delegations_test.go

Documentation: github.com/theupdateframework/go-tuf/client

     1  package client
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/theupdateframework/go-tuf/data"
    17  	"github.com/theupdateframework/go-tuf/util"
    18  	"github.com/theupdateframework/go-tuf/verify"
    19  )
    20  
    21  func TestGetTargetMeta(t *testing.T) {
    22  	verify.IsExpired = func(t time.Time) bool { return false }
    23  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
    24  	defer func() { assert.Nil(t, closer()) }()
    25  	_, err := c.Update()
    26  	assert.Nil(t, err)
    27  
    28  	f, err := c.getTargetFileMeta("f.txt")
    29  	assert.Nil(t, err)
    30  	hash := sha256.Sum256([]byte("Contents: f.txt"))
    31  	assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
    32  
    33  	f, err = c.getTargetFileMeta("targets.txt")
    34  	assert.Nil(t, err)
    35  	hash = sha256.Sum256([]byte("Contents: targets.txt"))
    36  	assert.Equal(t, data.HexBytes(hash[:]), f.Hashes["sha256"])
    37  }
    38  
    39  func TestMaxDelegations(t *testing.T) {
    40  	verify.IsExpired = func(t time.Time) bool { return false }
    41  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
    42  	defer func() { assert.Nil(t, closer()) }()
    43  	_, err := c.Update()
    44  	assert.Nil(t, err)
    45  	c.MaxDelegations = 2
    46  	_, err = c.getTargetFileMeta("c.txt")
    47  	assert.Equal(t, ErrMaxDelegations{Target: "c.txt", MaxDelegations: 2, SnapshotVersion: 2}, err)
    48  }
    49  
    50  func TestMetaNotFound(t *testing.T) {
    51  	verify.IsExpired = func(t time.Time) bool { return false }
    52  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
    53  	defer func() { assert.Nil(t, closer()) }()
    54  	_, err := c.Update()
    55  	assert.Nil(t, err)
    56  	_, err = c.getTargetFileMeta("unknown.txt")
    57  	assert.Equal(t, ErrUnknownTarget{Name: "unknown.txt", SnapshotVersion: 2}, err)
    58  }
    59  
    60  type fakeRemote struct {
    61  	getMeta   func(name string) (stream io.ReadCloser, size int64, err error)
    62  	getTarget func(path string) (stream io.ReadCloser, size int64, err error)
    63  }
    64  
    65  func (f fakeRemote) GetMeta(name string) (stream io.ReadCloser, size int64, err error) {
    66  	return f.getMeta(name)
    67  }
    68  
    69  func (f fakeRemote) GetTarget(name string) (stream io.ReadCloser, size int64, err error) {
    70  	return f.getTarget(name)
    71  }
    72  
    73  func TestTargetsNotFound(t *testing.T) {
    74  	verify.IsExpired = func(t time.Time) bool { return false }
    75  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
    76  	defer func() { assert.Nil(t, closer()) }()
    77  	_, err := c.Update()
    78  	assert.Nil(t, err)
    79  
    80  	previousRemote := c.remote
    81  	newRemote := fakeRemote{
    82  		getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
    83  			if path == "1.c.json" {
    84  				return nil, 0, ErrNotFound{}
    85  			}
    86  			return previousRemote.GetMeta(path)
    87  		},
    88  		getTarget: previousRemote.GetTarget,
    89  	}
    90  	c.remote = newRemote
    91  
    92  	_, err = c.getTargetFileMeta("c.txt")
    93  	assert.Equal(t, ErrMissingRemoteMetadata{Name: "c.json"}, err)
    94  }
    95  
    96  func TestUnverifiedTargets(t *testing.T) {
    97  	verify.IsExpired = func(t time.Time) bool { return false }
    98  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
    99  	defer closer()
   100  	_, err := c.Update()
   101  	assert.Nil(t, err)
   102  
   103  	previousRemote := c.remote
   104  	newRemote := fakeRemote{
   105  		getMeta: func(path string) (stream io.ReadCloser, size int64, err error) {
   106  			if path == "1.c.json" {
   107  				// returns a snapshot that does not match
   108  				return previousRemote.GetMeta("1.d.json")
   109  			}
   110  			return previousRemote.GetMeta(path)
   111  		},
   112  		getTarget: previousRemote.GetTarget,
   113  	}
   114  	c.remote = newRemote
   115  
   116  	_, err = c.getTargetFileMeta("c.txt")
   117  	assert.Equal(t, ErrDecodeFailed{File: "c.json", Err: verify.ErrRoleThreshold{Expected: 1, Actual: 0}}, err)
   118  }
   119  
   120  func TestPersistedMeta(t *testing.T) {
   121  	verify.IsExpired = func(t time.Time) bool { return false }
   122  	c, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture3LevelDelegation")
   123  	defer closer()
   124  	_, err := c.Update()
   125  	assert.Nil(t, err)
   126  
   127  	_, err = c.local.GetMeta()
   128  	assert.Nil(t, err)
   129  
   130  	type expectedTargets struct {
   131  		name    string
   132  		version int64
   133  	}
   134  	var persistedTests = []struct {
   135  		file          string
   136  		targets       []expectedTargets
   137  		downloadError error
   138  		targetError   error
   139  		fileContent   string
   140  	}{
   141  		{
   142  			file: "unknown",
   143  			targets: []expectedTargets{
   144  				{
   145  					name:    "targets.json",
   146  					version: 2,
   147  				},
   148  			},
   149  			downloadError: ErrUnknownTarget{Name: "unknown", SnapshotVersion: 2},
   150  			targetError:   ErrNotFound{File: "unknown"},
   151  			fileContent:   "",
   152  		},
   153  		{
   154  			file: "b.txt",
   155  			targets: []expectedTargets{
   156  				{
   157  					name:    "targets.json",
   158  					version: 2,
   159  				},
   160  				{
   161  					name:    "a.json",
   162  					version: 1,
   163  				},
   164  				{
   165  					name:    "b.json",
   166  					version: 1,
   167  				},
   168  			},
   169  			downloadError: nil,
   170  			targetError:   nil,
   171  			fileContent:   "Contents: b.txt",
   172  		},
   173  		{
   174  			file: "f.txt",
   175  			targets: []expectedTargets{
   176  				{
   177  					name:    "targets.json",
   178  					version: 2,
   179  				},
   180  				{
   181  					name:    "a.json",
   182  					version: 1,
   183  				},
   184  				{
   185  					name:    "b.json",
   186  					version: 1,
   187  				},
   188  				{
   189  					name:    "c.json",
   190  					version: 1,
   191  				},
   192  				{
   193  					name:    "d.json",
   194  					version: 1,
   195  				},
   196  				{
   197  					name:    "e.json",
   198  					version: 1,
   199  				},
   200  				{
   201  					name:    "f.json",
   202  					version: 1,
   203  				},
   204  			},
   205  			downloadError: nil,
   206  			targetError:   nil,
   207  			fileContent:   "Contents: f.txt",
   208  		},
   209  	}
   210  
   211  	for _, tt := range persistedTests {
   212  		t.Run("search "+tt.file, func(t *testing.T) {
   213  			var dest testDestination
   214  			err = c.Download(tt.file, &dest)
   215  			assert.Equal(t, tt.downloadError, err)
   216  			assert.Equal(t, tt.fileContent, dest.String())
   217  
   218  			target, err := c.Target(tt.file)
   219  			assert.Equal(t, tt.targetError, err)
   220  			if tt.targetError == nil {
   221  				meta, err := util.GenerateTargetFileMeta(strings.NewReader(tt.fileContent), target.HashAlgorithms()...)
   222  				assert.Nil(t, err)
   223  				assert.Nil(t, util.TargetFileMetaEqual(target, meta))
   224  			}
   225  
   226  			p, err := c.local.GetMeta()
   227  			assert.Nil(t, err)
   228  			persisted := copyStore(p)
   229  			persistedLocal := copyStore(c.localMeta)
   230  			// trim non targets metas
   231  			for _, notTargets := range []string{"root.json", "snapshot.json", "timestamp.json"} {
   232  				delete(persisted, notTargets)
   233  				delete(persistedLocal, notTargets)
   234  			}
   235  			for _, targets := range tt.targets {
   236  				// Test local store
   237  				storedVersion, err := versionOfStoredTargets(targets.name, persisted)
   238  				assert.Equal(t, targets.version, storedVersion)
   239  				assert.Nil(t, err)
   240  				delete(persisted, targets.name)
   241  
   242  				// Test localMeta
   243  				storedVersion, err = versionOfStoredTargets(targets.name, persistedLocal)
   244  				assert.Equal(t, targets.version, storedVersion)
   245  				assert.Nil(t, err)
   246  				delete(persistedLocal, targets.name)
   247  			}
   248  			assert.Empty(t, persisted)
   249  			assert.Empty(t, persistedLocal)
   250  		})
   251  	}
   252  }
   253  
   254  func TestGetDelegationPathWithNoTargetFile(t *testing.T) {
   255  	// In this test, we have created a target file c.txt for a delegation
   256  	// c.json, then we remove that target file and check if c.json is loaded
   257  	// in the localMeta. It shouldn't as it has no target file at all and shouldn't
   258  	// be used.
   259  	verify.IsExpired = func(t time.Time) bool { return false }
   260  	client, closer := initTestDelegationClient(t, "testdata/php-tuf-fixtures/TUFTestFixture2LevelDelegation")
   261  	defer closer()
   262  	_, err := client.Update()
   263  	assert.Nil(t, err)
   264  
   265  	err = client.getLocalMeta()
   266  	assert.Nil(t, err)
   267  
   268  	_, ok := client.localMeta["a.json"]
   269  	assert.True(t, ok)
   270  
   271  	_, ok = client.localMeta["b.json"]
   272  	assert.True(t, ok)
   273  
   274  	_, ok = client.localMeta["c.json"]
   275  	assert.False(t, ok)
   276  }
   277  
   278  func versionOfStoredTargets(name string, store map[string]json.RawMessage) (int64, error) {
   279  	rawTargets, ok := store[name]
   280  	if !ok {
   281  		return 0, nil
   282  	}
   283  	s := &data.Signed{}
   284  	if err := json.Unmarshal(rawTargets, s); err != nil {
   285  		return 0, err
   286  	}
   287  	targets := &data.Targets{}
   288  	if err := json.Unmarshal(s.Signed, targets); err != nil {
   289  		return 0, err
   290  	}
   291  	return targets.Version, nil
   292  }
   293  
   294  func initTestDelegationClient(t *testing.T, dirPrefix string) (*Client, func() error) {
   295  	serverDir := dirPrefix + "/server"
   296  	initialStateDir := dirPrefix + "/client/metadata/current"
   297  	l, err := net.Listen("tcp", "127.0.0.1:0")
   298  	assert.Nil(t, err)
   299  	addr := l.Addr().String()
   300  	go http.Serve(l, http.FileServer(http.Dir(serverDir)))
   301  
   302  	opts := &HTTPRemoteOptions{
   303  		MetadataPath: "metadata",
   304  		TargetsPath:  "targets",
   305  	}
   306  	remote, err := HTTPRemoteStore(fmt.Sprintf("http://%s/", addr), opts, nil)
   307  	assert.Nil(t, err)
   308  
   309  	c := NewClient(MemoryLocalStore(), remote)
   310  	rawFile, err := os.ReadFile(initialStateDir + "/" + "root.json")
   311  	assert.Nil(t, err)
   312  	assert.Nil(t, c.Init(rawFile))
   313  	files, err := os.ReadDir(initialStateDir)
   314  	assert.Nil(t, err)
   315  
   316  	// load local files
   317  	for _, f := range files {
   318  		if f.IsDir() {
   319  			continue
   320  		}
   321  		name := f.Name()
   322  		// ignoring consistent snapshot when loading initial state
   323  		if len(strings.Split(name, ".")) < 3 && strings.HasSuffix(name, ".json") {
   324  			rawFile, err := os.ReadFile(initialStateDir + "/" + name)
   325  			assert.Nil(t, err)
   326  			assert.Nil(t, c.local.SetMeta(name, rawFile))
   327  		}
   328  	}
   329  	return c, l.Close
   330  }
   331  
   332  func copyStore(store map[string]json.RawMessage) map[string]json.RawMessage {
   333  	new := make(map[string]json.RawMessage, len(store))
   334  	for k, raw := range store {
   335  		newRaw := make([]byte, len(raw))
   336  		copy(newRaw, []byte(raw))
   337  		new[k] = json.RawMessage(newRaw)
   338  	}
   339  	return new
   340  }
   341  

View as plain text