...

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

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

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  
    11  	"github.com/theupdateframework/go-tuf/data"
    12  	"github.com/theupdateframework/go-tuf/internal/roles"
    13  	"github.com/theupdateframework/go-tuf/util"
    14  	"github.com/theupdateframework/go-tuf/verify"
    15  )
    16  
    17  const (
    18  	// This is the upper limit in bytes we will use to limit the download
    19  	// size of the root/timestamp roles, since we might not don't know how
    20  	// big it is.
    21  	defaultRootDownloadLimit      = 512000
    22  	defaultTimestampDownloadLimit = 16384
    23  	defaultMaxDelegations         = 32
    24  	defaultMaxRootRotations       = 1e3
    25  )
    26  
    27  // LocalStore is local storage for downloaded top-level metadata.
    28  type LocalStore interface {
    29  	io.Closer
    30  
    31  	// GetMeta returns top-level metadata from local storage. The keys are
    32  	// in the form `ROLE.json`, with ROLE being a valid top-level role.
    33  	GetMeta() (map[string]json.RawMessage, error)
    34  
    35  	// SetMeta persists the given top-level metadata in local storage, the
    36  	// name taking the same format as the keys returned by GetMeta.
    37  	SetMeta(name string, meta json.RawMessage) error
    38  
    39  	// DeleteMeta deletes a given metadata.
    40  	DeleteMeta(name string) error
    41  }
    42  
    43  // RemoteStore downloads top-level metadata and target files from a remote
    44  // repository.
    45  type RemoteStore interface {
    46  	// GetMeta downloads the given metadata from remote storage.
    47  	//
    48  	// `name` is the filename of the metadata (e.g. "root.json")
    49  	//
    50  	// `err` is ErrNotFound if the given file does not exist.
    51  	//
    52  	// `size` is the size of the stream, -1 indicating an unknown length.
    53  	GetMeta(name string) (stream io.ReadCloser, size int64, err error)
    54  
    55  	// GetTarget downloads the given target file from remote storage.
    56  	//
    57  	// `path` is the path of the file relative to the root of the remote
    58  	//        targets directory (e.g. "/path/to/file.txt").
    59  	//
    60  	// `err` is ErrNotFound if the given file does not exist.
    61  	//
    62  	// `size` is the size of the stream, -1 indicating an unknown length.
    63  	GetTarget(path string) (stream io.ReadCloser, size int64, err error)
    64  }
    65  
    66  // Client provides methods for fetching updates from a remote repository and
    67  // downloading remote target files.
    68  type Client struct {
    69  	local  LocalStore
    70  	remote RemoteStore
    71  
    72  	// The following four fields represent the versions of metatdata either
    73  	// from local storage or from recently downloaded metadata
    74  	rootVer      int64
    75  	targetsVer   int64
    76  	snapshotVer  int64
    77  	timestampVer int64
    78  
    79  	// targets is the list of available targets, either from local storage
    80  	// or from recently downloaded targets metadata
    81  	targets data.TargetFiles
    82  
    83  	// localMeta is the raw metadata from local storage and is used to
    84  	// check whether remote metadata is present locally
    85  	localMeta map[string]json.RawMessage
    86  
    87  	// db is a key DB used for verifying metadata
    88  	db *verify.DB
    89  
    90  	// consistentSnapshot indicates whether the remote storage is using
    91  	// consistent snapshots (as specified in root.json)
    92  	consistentSnapshot bool
    93  
    94  	// MaxDelegations limits by default the number of delegations visited for any
    95  	// target
    96  	MaxDelegations int
    97  
    98  	// MaxRootRotations limits the number of downloaded roots in 1.0.19 root updater
    99  	MaxRootRotations int
   100  }
   101  
   102  func NewClient(local LocalStore, remote RemoteStore) *Client {
   103  	return &Client{
   104  		local:            local,
   105  		remote:           remote,
   106  		MaxDelegations:   defaultMaxDelegations,
   107  		MaxRootRotations: defaultMaxRootRotations,
   108  	}
   109  }
   110  
   111  // Init initializes a local repository from root metadata.
   112  //
   113  // The root's keys are extracted from the root and saved in local storage.
   114  // Root expiration is not checked.
   115  // It is expected that rootJSON was securely distributed with the software
   116  // being updated.
   117  func (c *Client) Init(rootJSON []byte) error {
   118  	err := c.loadAndVerifyRootMeta(rootJSON, true /*ignoreExpiredCheck*/)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	return c.local.SetMeta("root.json", rootJSON)
   123  }
   124  
   125  // Update downloads and verifies remote metadata and returns updated targets.
   126  // It always performs root update (5.2 and 5.3) section of the v1.0.19 spec.
   127  //
   128  // https://theupdateframework.github.io/specification/v1.0.19/index.html#load-trusted-root
   129  func (c *Client) Update() (data.TargetFiles, error) {
   130  	if err := c.UpdateRoots(); err != nil {
   131  		if _, ok := err.(verify.ErrExpired); ok {
   132  			// For backward compatibility, we wrap the ErrExpired inside
   133  			// ErrDecodeFailed.
   134  			return nil, ErrDecodeFailed{"root.json", err}
   135  		}
   136  		return nil, err
   137  	}
   138  
   139  	// Load trusted metadata files, if any, and verify them against the latest root
   140  	c.getLocalMeta()
   141  
   142  	// 5.4.1 - Download the timestamp metadata
   143  	timestampJSON, err := c.downloadMetaUnsafe("timestamp.json", defaultTimestampDownloadLimit)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	// 5.4.(2,3 and 4) - Verify timestamp against various attacks
   148  	// Returns the extracted snapshot metadata
   149  	snapshotMeta, sameTimestampVersion, err := c.decodeTimestamp(timestampJSON)
   150  	if sameTimestampVersion {
   151  		// The new timestamp.json file had the same version; we don't need to
   152  		// update, so bail early.
   153  		return c.targets, nil
   154  	}
   155  
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	// 5.4.5 - Persist the timestamp metadata
   160  	if err := c.local.SetMeta("timestamp.json", timestampJSON); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// 5.5.1 - Download snapshot metadata
   165  	// 5.5.2 and 5.5.4 - Check against timestamp role's snapshot hash and version
   166  	snapshotJSON, err := c.downloadMetaFromTimestamp("snapshot.json", snapshotMeta)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	// 5.5.(3,5 and 6) - Verify snapshot against various attacks
   171  	// Returns the extracted metadata files
   172  	snapshotMetas, err := c.decodeSnapshot(snapshotJSON)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	// 5.5.7 - Persist snapshot metadata
   177  	if err := c.local.SetMeta("snapshot.json", snapshotJSON); err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	// If we don't have the targets.json, download it, determine updated
   182  	// targets and save targets.json in local storage
   183  	var updatedTargets data.TargetFiles
   184  	targetsMeta := snapshotMetas["targets.json"]
   185  	if !c.hasMetaFromSnapshot("targets.json", targetsMeta) {
   186  		// 5.6.1 - Download the top-level targets metadata file
   187  		// 5.6.2 and 5.6.4 - Check against snapshot role's targets hash and version
   188  		targetsJSON, err := c.downloadMetaFromSnapshot("targets.json", targetsMeta)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  		// 5.6.(3 and 5) - Verify signatures and check against freeze attack
   193  		updatedTargets, err = c.decodeTargets(targetsJSON)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		// 5.6.6 - Persist targets metadata
   198  		if err := c.local.SetMeta("targets.json", targetsJSON); err != nil {
   199  			return nil, err
   200  		}
   201  	}
   202  
   203  	return updatedTargets, nil
   204  }
   205  
   206  func (c *Client) UpdateRoots() error {
   207  	// https://theupdateframework.github.io/specification/v1.0.19/index.html#load-trusted-root
   208  	// 5.2 Load the trusted root metadata file. We assume that a good,
   209  	// trusted copy of this file was shipped with the package manager
   210  	// or software updater using an out-of-band process.
   211  	if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ true); err != nil {
   212  		return err
   213  	}
   214  	m, err := c.local.GetMeta()
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	type KeyInfo struct {
   220  		KeyIDs    map[string]bool
   221  		Threshold int
   222  	}
   223  
   224  	// Prepare for 5.3.11: If the timestamp and / or snapshot keys have been rotated,
   225  	// then delete the trusted timestamp and snapshot metadata files.
   226  	getKeyInfo := func(role string) KeyInfo {
   227  		keyIDs := make(map[string]bool)
   228  		for k := range c.db.GetRole(role).KeyIDs {
   229  			keyIDs[k] = true
   230  		}
   231  		return KeyInfo{keyIDs, c.db.GetRole(role).Threshold}
   232  	}
   233  
   234  	// The nonRootKeyInfo looks like this:
   235  	// {
   236  	//	"timestamp": {KeyIDs={"KEYID1": true, "KEYID2": true}, Threshold=2},
   237  	//	"snapshot": {KeyIDs={"KEYID3": true}, Threshold=1},
   238  	//	"targets": {KeyIDs={"KEYID4": true, "KEYID5": true, "KEYID6": true}, Threshold=1}
   239  	// }
   240  
   241  	nonRootKeyInfo := map[string]KeyInfo{"timestamp": {}, "snapshot": {}, "targets": {}}
   242  	for k := range nonRootKeyInfo {
   243  		nonRootKeyInfo[k] = getKeyInfo(k)
   244  	}
   245  
   246  	// 5.3.1 Temorarily turn on the consistent snapshots in order to download
   247  	// versioned root metadata files as described next.
   248  	consistentSnapshot := c.consistentSnapshot
   249  	c.consistentSnapshot = true
   250  
   251  	nRootMetadata := m["root.json"]
   252  
   253  	// https://theupdateframework.github.io/specification/v1.0.19/index.html#update-root
   254  
   255  	// 5.3.1 Since it may now be signed using entirely different keys,
   256  	// the client MUST somehow be able to establish a trusted line of
   257  	// continuity to the latest set of keys (see § 6.1 Key
   258  	// management and migration). To do so, the client MUST
   259  	// download intermediate root metadata files, until the
   260  	// latest available one is reached. Therefore, it MUST
   261  	// temporarily turn on consistent snapshots in order to
   262  	// download versioned root metadata files as described next.
   263  
   264  	// This loop returns on error or breaks after downloading the lastest root metadata.
   265  	// 5.3.2 Let N denote the version number of the trusted root metadata file.
   266  	for i := 0; i < c.MaxRootRotations; i++ {
   267  		// 5.3.3 Try downloading version nPlusOne of the root metadata file.
   268  		// NOTE: as a side effect, we do update c.rootVer to nPlusOne between iterations.
   269  		nPlusOne := c.rootVer + 1
   270  		nPlusOneRootPath := util.VersionedPath("root.json", nPlusOne)
   271  		nPlusOneRootMetadata, err := c.downloadMetaUnsafe(nPlusOneRootPath, defaultRootDownloadLimit)
   272  
   273  		if err != nil {
   274  			if _, ok := err.(ErrMissingRemoteMetadata); ok {
   275  				// stop when the next root can't be downloaded
   276  				break
   277  			}
   278  			return err
   279  		}
   280  
   281  		// 5.3.4 Check for an arbitrary software attack.
   282  		// 5.3.4.1 Check that N signed N+1
   283  		nPlusOneRootMetadataSigned, err := c.verifyRoot(nRootMetadata, nPlusOneRootMetadata)
   284  		if err != nil {
   285  			return err
   286  		}
   287  
   288  		// 5.3.4.2 check that N+1 signed itself.
   289  		if _, err := c.verifyRoot(nPlusOneRootMetadata, nPlusOneRootMetadata); err != nil {
   290  			// 5.3.6 Note that the expiration of the new (intermediate) root
   291  			// metadata file does not matter yet, because we will check for
   292  			// it in step 5.3.10.
   293  			return err
   294  		}
   295  
   296  		// 5.3.5 Check for a rollback attack. Here, we check that nPlusOneRootMetadataSigned.version == nPlusOne.
   297  		if nPlusOneRootMetadataSigned.Version != nPlusOne {
   298  			return verify.ErrWrongVersion{
   299  				Given:    nPlusOneRootMetadataSigned.Version,
   300  				Expected: nPlusOne,
   301  			}
   302  		}
   303  
   304  		// 5.3.7 Set the trusted root metadata file to the new root metadata file.
   305  		c.rootVer = nPlusOneRootMetadataSigned.Version
   306  		// NOTE: following up on 5.3.1, we want to always have consistent snapshots on for the duration
   307  		// of root rotation. AFTER the rotation is over, we will set it to the value of the last root.
   308  		consistentSnapshot = nPlusOneRootMetadataSigned.ConsistentSnapshot
   309  		// 5.3.8 Persist root metadata. The client MUST write the file to non-volatile storage as FILENAME.EXT (e.g. root.json).
   310  		// NOTE: Internally, setMeta stores metadata in LevelDB in a persistent manner.
   311  		if err := c.local.SetMeta("root.json", nPlusOneRootMetadata); err != nil {
   312  			return err
   313  		}
   314  		nRootMetadata = nPlusOneRootMetadata
   315  		// 5.3.9 Repeat steps 5.3.2 to 5.3.9
   316  
   317  	} // End of the for loop.
   318  
   319  	// 5.3.10 Check for a freeze attack.
   320  	// NOTE: This will check for any, including freeze, attack.
   321  	if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ false); err != nil {
   322  		return err
   323  	}
   324  
   325  	countDeleted := func(s1 map[string]bool, s2 map[string]bool) int {
   326  		c := 0
   327  		for k := range s1 {
   328  			if _, ok := s2[k]; !ok {
   329  				c++
   330  			}
   331  		}
   332  		return c
   333  	}
   334  
   335  	// 5.3.11 To recover from fast-forward attack, certain metadata files need
   336  	// to be deleted if a threshold of keys are revoked.
   337  	// List of metadata that should be deleted per role if a threshold of keys
   338  	// are revoked:
   339  	// (based on the ongoing PR: https://github.com/mnm678/specification/tree/e50151d9df632299ddea364c4f44fe8ca9c10184)
   340  	// timestamp -> delete timestamp.json
   341  	// snapshot ->  delete timestamp.json and snapshot.json
   342  	// targets ->   delete snapshot.json and targets.json
   343  	//
   344  	// nonRootKeyInfo contains the keys and thresholds from root.json
   345  	// that were on disk before the root update process begins.
   346  	for topLevelRolename := range nonRootKeyInfo {
   347  		// ki contains the keys and thresholds from the latest downloaded root.json.
   348  		ki := getKeyInfo(topLevelRolename)
   349  		if countDeleted(nonRootKeyInfo[topLevelRolename].KeyIDs, ki.KeyIDs) >= nonRootKeyInfo[topLevelRolename].Threshold {
   350  			deleteMeta := map[string][]string{
   351  				"timestamp": {"timestamp.json"},
   352  				"snapshot":  {"timestamp.json", "snapshot.json"},
   353  				"targets":   {"snapshot.json", "targets.json"},
   354  			}
   355  
   356  			for _, r := range deleteMeta[topLevelRolename] {
   357  				c.local.DeleteMeta(r)
   358  			}
   359  		}
   360  	}
   361  
   362  	// 5.3.12 Set whether consistent snapshots are used as per the trusted root metadata file.
   363  	c.consistentSnapshot = consistentSnapshot
   364  	return nil
   365  }
   366  
   367  // getLocalMeta decodes and verifies metadata from local storage.
   368  // The verification of local files is purely for consistency, if an attacker
   369  // has compromised the local storage, there is no guarantee it can be trusted.
   370  // Before trying to load the metadata files, it clears the in-memory copy of the local metadata.
   371  // This is to insure that all of the loaded metadata files at the end are indeed verified by the latest root.
   372  // If some of the metadata files fail to load it will proceed with trying to load the rest,
   373  // but still return an error at the end, if such occurred. Otherwise returns nil.
   374  func (c *Client) getLocalMeta() error {
   375  	var retErr error
   376  	loadFailed := false
   377  	// Clear the in-memory copy of the local metadata. The goal is to reload and take into account
   378  	// only the metadata files that are verified by the latest root. Otherwise, their content should
   379  	// be ignored.
   380  	c.localMeta = make(map[string]json.RawMessage)
   381  
   382  	// Load the latest root meta
   383  	if err := c.loadAndVerifyLocalRootMeta( /*ignoreExpiredCheck=*/ false); err != nil {
   384  		return err
   385  	}
   386  
   387  	// Load into memory the existing meta, if any, from the local storage
   388  	meta, err := c.local.GetMeta()
   389  	if err != nil {
   390  		return nil
   391  	}
   392  
   393  	// Verify the top-level metadata (timestamp, snapshot and targets) against the latest root and load it, if okay
   394  	if timestampJSON, ok := meta["timestamp.json"]; ok {
   395  		timestamp := &data.Timestamp{}
   396  		if err := c.db.UnmarshalTrusted(timestampJSON, timestamp, "timestamp"); err != nil {
   397  			loadFailed = true
   398  			retErr = err
   399  		} else {
   400  			c.localMeta["timestamp.json"] = meta["timestamp.json"]
   401  			c.timestampVer = timestamp.Version
   402  		}
   403  	}
   404  
   405  	snapshot := &data.Snapshot{}
   406  	if snapshotJSON, ok := meta["snapshot.json"]; ok {
   407  		if err := c.db.UnmarshalTrusted(snapshotJSON, snapshot, "snapshot"); err != nil {
   408  			loadFailed = true
   409  			retErr = err
   410  		} else {
   411  			c.localMeta["snapshot.json"] = meta["snapshot.json"]
   412  			c.snapshotVer = snapshot.Version
   413  		}
   414  	}
   415  
   416  	if targetsJSON, ok := meta["targets.json"]; ok {
   417  		targets := &data.Targets{}
   418  		if err := c.db.UnmarshalTrusted(targetsJSON, targets, "targets"); err != nil {
   419  			loadFailed = true
   420  			retErr = err
   421  		} else {
   422  			c.localMeta["targets.json"] = meta["targets.json"]
   423  			c.targetsVer = targets.Version
   424  			// FIXME(TUF-0.9) temporarily support files with leading path separators.
   425  			// c.targets = targets.Targets
   426  			c.loadTargets(targets.Targets)
   427  		}
   428  	}
   429  
   430  	if loadFailed {
   431  		// If any of the metadata failed to be verified, return the reason for that failure
   432  		// and fail fast before delegated targets
   433  		return retErr
   434  	}
   435  
   436  	// verifiedDelegatedTargets is a set of verified delegated targets
   437  	verifiedDelegatedTargets := make(map[string]bool)
   438  	for fileName := range meta {
   439  		if !verifiedDelegatedTargets[fileName] && roles.IsDelegatedTargetsManifest(fileName) {
   440  			if delegationPath, err := c.getDelegationPathFromRaw(snapshot, meta[fileName]); err != nil {
   441  				loadFailed = true
   442  				retErr = err
   443  			} else {
   444  				// Every delegated targets in the path has been verified
   445  				// as a side effect of getDelegationPathFromRaw
   446  				for _, key := range delegationPath {
   447  					fileName := fmt.Sprintf("%s.json", key)
   448  					verifiedDelegatedTargets[fileName] = true
   449  				}
   450  			}
   451  		}
   452  	}
   453  
   454  	for fileName := range verifiedDelegatedTargets {
   455  		c.localMeta[fileName] = meta[fileName]
   456  	}
   457  
   458  	if loadFailed {
   459  		// If any of the metadata failed to be verified, return the reason for that failure
   460  		return retErr
   461  	}
   462  	return nil
   463  }
   464  
   465  // getDelegationPathFromRaw verifies a delegated targets against
   466  // a given snapshot and returns an error if it's invalid
   467  //
   468  // Delegation must have targets to get a path, else an empty list
   469  // will be returned: this is because the delegation iterator is leveraged.
   470  //
   471  // Concrete example:
   472  // targets
   473  // └── a.json
   474  //   └── b.json
   475  //      └── c.json
   476  //        └── target_file.txt
   477  //
   478  // If you try to use that function on "a.json" or "b.json", it'll return an empty list
   479  // with no error, as neither of them declare a target file
   480  // On the other hand, if you use that function on "c.json", it'll return & verify
   481  // [c.json, b.json, a.json]. Running that function on every delegated targets
   482  // guarantees that if a delegated targets is in the path of a target file, then it will
   483  // appear at least once in the result
   484  func (c *Client) getDelegationPathFromRaw(snapshot *data.Snapshot, delegatedTargetsJSON json.RawMessage) ([]string, error) {
   485  	// unmarshal the delegated targets first without verifying as
   486  	// we need at least one targets file name to leverage the
   487  	// getTargetFileMetaDelegationPath method
   488  	s := &data.Signed{}
   489  	if err := json.Unmarshal(delegatedTargetsJSON, s); err != nil {
   490  		return nil, err
   491  	}
   492  	targets := &data.Targets{}
   493  	if err := json.Unmarshal(s.Signed, targets); err != nil {
   494  		return nil, err
   495  	}
   496  	for targetPath := range targets.Targets {
   497  		// Gets target file from remote store
   498  		_, resp, err := c.getTargetFileMetaDelegationPath(targetPath, snapshot)
   499  		// We only need to test one targets file:
   500  		// - If it is valid, it means the delegated targets has been validated
   501  		// - If it is not, the delegated targets isn't valid
   502  		if errors.As(err, &ErrMissingRemoteMetadata{}) {
   503  			// As this function is used to fill the local store cache, the targets
   504  			// will be downloaded from the remote store as the local store cache is
   505  			// empty, meaning that the delegated targets may not exist anymore. In
   506  			// that case, we can't get the delegation path. Ignore the delegated targets.
   507  			return nil, nil
   508  		}
   509  		if errors.As(err, &ErrUnknownTarget{}) {
   510  			// As this function is used to fill the local store cache, the target file
   511  			// will be downloaded from the remote store as the local store cache is
   512  			// empty, meaning that the target file may not exist anymore. In
   513  			// that case, ignore the file and try another.
   514  			continue
   515  		}
   516  		return resp, err
   517  	}
   518  	return nil, nil
   519  }
   520  
   521  // loadAndVerifyLocalRootMeta decodes and verifies root metadata from
   522  // local storage and loads the top-level keys. This method first clears
   523  // the DB for top-level keys and then loads the new keys.
   524  func (c *Client) loadAndVerifyLocalRootMeta(ignoreExpiredCheck bool) error {
   525  	meta, err := c.local.GetMeta()
   526  	if err != nil {
   527  		return err
   528  	}
   529  	rootJSON, ok := meta["root.json"]
   530  	if !ok {
   531  		return ErrNoRootKeys
   532  	}
   533  	return c.loadAndVerifyRootMeta(rootJSON, ignoreExpiredCheck)
   534  }
   535  
   536  // loadAndVerifyRootMeta decodes and verifies root metadata and loads the top-level keys.
   537  // This method first clears the DB for top-level keys and then loads the new keys.
   538  func (c *Client) loadAndVerifyRootMeta(rootJSON []byte, ignoreExpiredCheck bool) error {
   539  	// unmarshal root.json without verifying as we need the root
   540  	// keys first
   541  	s := &data.Signed{}
   542  	if err := json.Unmarshal(rootJSON, s); err != nil {
   543  		return err
   544  	}
   545  	root := &data.Root{}
   546  	if err := json.Unmarshal(s.Signed, root); err != nil {
   547  		return err
   548  	}
   549  	ndb := verify.NewDB()
   550  	for id, k := range root.Keys {
   551  		if err := ndb.AddKey(id, k); err != nil {
   552  			return err
   553  		}
   554  	}
   555  	for name, role := range root.Roles {
   556  		if err := ndb.AddRole(name, role); err != nil {
   557  			return err
   558  		}
   559  	}
   560  	// Any trusted local root metadata version must be greater than 0.
   561  	if ignoreExpiredCheck {
   562  		if err := ndb.VerifyIgnoreExpiredCheck(s, "root", 0); err != nil {
   563  			return err
   564  		}
   565  	} else {
   566  		if err := ndb.Verify(s, "root", 0); err != nil {
   567  			return err
   568  		}
   569  	}
   570  	c.consistentSnapshot = root.ConsistentSnapshot
   571  	c.rootVer = root.Version
   572  	c.db = ndb
   573  	return nil
   574  }
   575  
   576  // verifyRoot verifies Signed section of the bJSON
   577  // using verification keys in aJSON.
   578  func (c *Client) verifyRoot(aJSON []byte, bJSON []byte) (*data.Root, error) {
   579  	aSigned := &data.Signed{}
   580  	if err := json.Unmarshal(aJSON, aSigned); err != nil {
   581  		return nil, err
   582  	}
   583  	aRoot := &data.Root{}
   584  	if err := json.Unmarshal(aSigned.Signed, aRoot); err != nil {
   585  		return nil, err
   586  	}
   587  
   588  	bSigned := &data.Signed{}
   589  	if err := json.Unmarshal(bJSON, bSigned); err != nil {
   590  		return nil, err
   591  	}
   592  	bRoot := &data.Root{}
   593  	if err := json.Unmarshal(bSigned.Signed, bRoot); err != nil {
   594  		return nil, err
   595  	}
   596  
   597  	ndb := verify.NewDB()
   598  	for id, k := range aRoot.Keys {
   599  		if err := ndb.AddKey(id, k); err != nil {
   600  			return nil, err
   601  		}
   602  	}
   603  	for name, role := range aRoot.Roles {
   604  		if err := ndb.AddRole(name, role); err != nil {
   605  			return nil, err
   606  		}
   607  	}
   608  
   609  	if err := ndb.VerifySignatures(bSigned, "root"); err != nil {
   610  		return nil, err
   611  	}
   612  	return bRoot, nil
   613  }
   614  
   615  // FIXME(TUF-0.9) TUF is considering removing support for target files starting
   616  // with a leading path separator. In order to be backwards compatible, we'll
   617  // just remove leading separators for now.
   618  func (c *Client) loadTargets(targets data.TargetFiles) {
   619  	c.targets = make(data.TargetFiles)
   620  	for name, meta := range targets {
   621  		c.targets[name] = meta
   622  		c.targets[util.NormalizeTarget(name)] = meta
   623  	}
   624  }
   625  
   626  // downloadMetaUnsafe downloads top-level metadata from remote storage without
   627  // verifying it's length and hashes (used for example to download timestamp.json
   628  // which has unknown size). It will download at most maxMetaSize bytes.
   629  func (c *Client) downloadMetaUnsafe(name string, maxMetaSize int64) ([]byte, error) {
   630  	r, size, err := c.remote.GetMeta(name)
   631  	if err != nil {
   632  		if IsNotFound(err) {
   633  			return nil, ErrMissingRemoteMetadata{name}
   634  		}
   635  		return nil, ErrDownloadFailed{name, err}
   636  	}
   637  	defer r.Close()
   638  
   639  	// return ErrMetaTooLarge if the reported size is greater than maxMetaSize
   640  	if size > maxMetaSize {
   641  		return nil, ErrMetaTooLarge{name, size, maxMetaSize}
   642  	}
   643  
   644  	// although the size has been checked above, use a LimitReader in case
   645  	// the reported size is inaccurate, or size is -1 which indicates an
   646  	// unknown length
   647  	return io.ReadAll(io.LimitReader(r, maxMetaSize))
   648  }
   649  
   650  // remoteGetFunc is the type of function the download method uses to download
   651  // remote files
   652  type remoteGetFunc func(string) (io.ReadCloser, int64, error)
   653  
   654  // downloadHashed tries to download the hashed prefixed version of the file.
   655  func (c *Client) downloadHashed(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
   656  	// try each hashed path in turn, and either return the contents,
   657  	// try the next one if a 404 is returned, or return an error
   658  	for _, path := range util.HashedPaths(file, hashes) {
   659  		r, size, err := get(path)
   660  		if err != nil {
   661  			if IsNotFound(err) {
   662  				continue
   663  			}
   664  			return nil, 0, err
   665  		}
   666  		return r, size, nil
   667  	}
   668  	return nil, 0, ErrNotFound{file}
   669  }
   670  
   671  // download downloads the given target file from remote storage using the get
   672  // function, adding hashes to the path if consistent snapshots are in use
   673  func (c *Client) downloadTarget(file string, get remoteGetFunc, hashes data.Hashes) (io.ReadCloser, int64, error) {
   674  	if c.consistentSnapshot {
   675  		return c.downloadHashed(file, get, hashes)
   676  	} else {
   677  		return get(file)
   678  	}
   679  }
   680  
   681  // downloadVersionedMeta downloads top-level metadata from remote storage and
   682  // verifies it using the given file metadata.
   683  func (c *Client) downloadMeta(name string, version int64, m data.FileMeta) ([]byte, error) {
   684  	r, size, err := func() (io.ReadCloser, int64, error) {
   685  		if c.consistentSnapshot {
   686  			path := util.VersionedPath(name, version)
   687  			r, size, err := c.remote.GetMeta(path)
   688  			if err == nil {
   689  				return r, size, nil
   690  			}
   691  
   692  			return nil, 0, err
   693  		} else {
   694  			return c.remote.GetMeta(name)
   695  		}
   696  	}()
   697  	if err != nil {
   698  		if IsNotFound(err) {
   699  			return nil, ErrMissingRemoteMetadata{name}
   700  		}
   701  		return nil, err
   702  	}
   703  	defer r.Close()
   704  
   705  	// return ErrWrongSize if the reported size is known and incorrect
   706  	var stream io.Reader
   707  	if m.Length != 0 {
   708  		if size >= 0 && size != m.Length {
   709  			return nil, ErrWrongSize{name, size, m.Length}
   710  		}
   711  
   712  		// wrap the data in a LimitReader so we download at most m.Length bytes
   713  		stream = io.LimitReader(r, m.Length)
   714  	} else {
   715  		stream = r
   716  	}
   717  
   718  	return io.ReadAll(stream)
   719  }
   720  
   721  func (c *Client) downloadMetaFromSnapshot(name string, m data.SnapshotFileMeta) ([]byte, error) {
   722  	b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
   723  	if err != nil {
   724  		return nil, err
   725  	}
   726  
   727  	// 5.6.2 – Check length and hashes of fetched bytes *before* parsing metadata
   728  	if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
   729  		return nil, ErrDownloadFailed{name, err}
   730  	}
   731  
   732  	meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
   733  	if err != nil {
   734  		return nil, err
   735  	}
   736  
   737  	// 5.6.4 - Check against snapshot role's version
   738  	if err := util.VersionEqual(meta.Version, m.Version); err != nil {
   739  		return nil, ErrDownloadFailed{name, err}
   740  	}
   741  
   742  	return b, nil
   743  }
   744  
   745  func (c *Client) downloadMetaFromTimestamp(name string, m data.TimestampFileMeta) ([]byte, error) {
   746  	b, err := c.downloadMeta(name, m.Version, data.FileMeta{Length: m.Length, Hashes: m.Hashes})
   747  	if err != nil {
   748  		return nil, err
   749  	}
   750  
   751  	// 5.2.2. – Check length and hashes of fetched bytes *before* parsing metadata
   752  	if err := util.BytesMatchLenAndHashes(b, m.Length, m.Hashes); err != nil {
   753  		return nil, ErrDownloadFailed{name, err}
   754  	}
   755  
   756  	meta, err := util.GenerateTimestampFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  
   761  	// 5.5.4 - Check against timestamp role's version
   762  	if err := util.VersionEqual(meta.Version, m.Version); err != nil {
   763  		return nil, ErrDownloadFailed{name, err}
   764  	}
   765  
   766  	return b, nil
   767  }
   768  
   769  // decodeSnapshot decodes and verifies snapshot metadata, and returns the new
   770  // root and targets file meta.
   771  func (c *Client) decodeSnapshot(b json.RawMessage) (data.SnapshotFiles, error) {
   772  	snapshot := &data.Snapshot{}
   773  	// 5.5.(3 and 6) - Verify it's signed correctly and it's not expired
   774  	if err := c.db.Unmarshal(b, snapshot, "snapshot", c.snapshotVer); err != nil {
   775  		return data.SnapshotFiles{}, ErrDecodeFailed{"snapshot.json", err}
   776  	}
   777  	// 5.5.5 - Check for top-level targets rollback attack
   778  	// Verify explicitly that current targets meta version is less than or equal to the new one
   779  	if snapshot.Meta["targets.json"].Version < c.targetsVer {
   780  		return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: snapshot.Meta["targets.json"].Version, Current: c.targetsVer}
   781  	}
   782  
   783  	// 5.5.5 - Get the local/trusted snapshot metadata, if any, and check all target metafiles against rollback attack
   784  	// In case the local snapshot metadata was not verified by the keys in the latest root during getLocalMeta(),
   785  	// snapshot.json won't be present in c.localMeta and thus this check will not be processed.
   786  	if snapshotJSON, ok := c.localMeta["snapshot.json"]; ok {
   787  		currentSnapshot := &data.Snapshot{}
   788  		if err := c.db.UnmarshalTrusted(snapshotJSON, currentSnapshot, "snapshot"); err != nil {
   789  			return data.SnapshotFiles{}, err
   790  		}
   791  		// 5.5.5 - Check for rollback attacks in both top-level and delegated targets roles (note that the Meta object includes both)
   792  		for path, local := range currentSnapshot.Meta {
   793  			if newMeta, ok := snapshot.Meta[path]; ok {
   794  				// 5.5.5 - Check for rollback attack
   795  				if newMeta.Version < local.Version {
   796  					return data.SnapshotFiles{}, verify.ErrLowVersion{Actual: newMeta.Version, Current: local.Version}
   797  				}
   798  			} else {
   799  				// 5.5.5 - Abort the update if a target file has been removed from the new snapshot file
   800  				return data.SnapshotFiles{}, verify.ErrMissingTargetFile
   801  			}
   802  		}
   803  	}
   804  	// At this point we can trust the new snapshot, the top-level targets, and any delegated targets versions it refers to
   805  	// so we can update the client's trusted versions and proceed with persisting the new snapshot metadata
   806  	// c.snapshotVer was already set when we verified the timestamp metadata
   807  	c.targetsVer = snapshot.Meta["targets.json"].Version
   808  	return snapshot.Meta, nil
   809  }
   810  
   811  // decodeTargets decodes and verifies targets metadata, sets c.targets and
   812  // returns updated targets.
   813  func (c *Client) decodeTargets(b json.RawMessage) (data.TargetFiles, error) {
   814  	targets := &data.Targets{}
   815  	// 5.6.(3 and 5) - Verify signatures and check against freeze attack
   816  	if err := c.db.Unmarshal(b, targets, "targets", c.targetsVer); err != nil {
   817  		return nil, ErrDecodeFailed{"targets.json", err}
   818  	}
   819  	// Generate a list with the updated targets
   820  	updatedTargets := make(data.TargetFiles)
   821  	for path, meta := range targets.Targets {
   822  		if local, ok := c.targets[path]; ok {
   823  			if err := util.TargetFileMetaEqual(local, meta); err == nil {
   824  				continue
   825  			}
   826  		}
   827  		updatedTargets[path] = meta
   828  	}
   829  	// c.targetsVer was already updated when we verified the snapshot metadata
   830  	// FIXME(TUF-0.9) temporarily support files with leading path separators.
   831  	// c.targets = targets.Targets
   832  	c.loadTargets(targets.Targets)
   833  	return updatedTargets, nil
   834  }
   835  
   836  // decodeTimestamp decodes and verifies timestamp metadata, and returns the
   837  // new snapshot file meta and signals whether the update should be aborted early
   838  // (the new timestamp has the same version as the old one, so there's no need to
   839  // complete the update).
   840  func (c *Client) decodeTimestamp(b json.RawMessage) (data.TimestampFileMeta, bool, error) {
   841  	timestamp := &data.Timestamp{}
   842  
   843  	if err := c.db.Unmarshal(b, timestamp, "timestamp", c.timestampVer); err != nil {
   844  		return data.TimestampFileMeta{}, false, ErrDecodeFailed{"timestamp.json", err}
   845  	}
   846  	// 5.4.3.1  - Check for timestamp rollback attack
   847  	// We already checked for timestamp.Version < c.timestampVer in the Unmarshal call above.
   848  	// Here, we're checking for version equality, which indicates that we can abandon this update.
   849  	if timestamp.Version == c.timestampVer {
   850  		return data.TimestampFileMeta{}, true, nil
   851  	}
   852  	// 5.4.3.2 - Check for snapshot rollback attack
   853  	// Verify that the current snapshot meta version is less than or equal to the new one
   854  	if timestamp.Meta["snapshot.json"].Version < c.snapshotVer {
   855  		return data.TimestampFileMeta{}, false, verify.ErrLowVersion{Actual: timestamp.Meta["snapshot.json"].Version, Current: c.snapshotVer}
   856  	}
   857  	// At this point we can trust the new timestamp and the snapshot version it refers to
   858  	// so we can update the client's trusted versions and proceed with persisting the new timestamp
   859  	c.timestampVer = timestamp.Version
   860  	c.snapshotVer = timestamp.Meta["snapshot.json"].Version
   861  	return timestamp.Meta["snapshot.json"], false, nil
   862  }
   863  
   864  // hasMetaFromSnapshot checks whether local metadata has the given meta
   865  func (c *Client) hasMetaFromSnapshot(name string, m data.SnapshotFileMeta) bool {
   866  	_, ok := c.localMetaFromSnapshot(name, m)
   867  	return ok
   868  }
   869  
   870  // localMetaFromSnapshot returns localmetadata if it matches the snapshot
   871  func (c *Client) localMetaFromSnapshot(name string, m data.SnapshotFileMeta) (json.RawMessage, bool) {
   872  	b, ok := c.localMeta[name]
   873  	if !ok {
   874  		return nil, false
   875  	}
   876  	meta, err := util.GenerateSnapshotFileMeta(bytes.NewReader(b), m.Hashes.HashAlgorithms()...)
   877  	if err != nil {
   878  		return nil, false
   879  	}
   880  	err = util.SnapshotFileMetaEqual(meta, m)
   881  	return b, err == nil
   882  }
   883  
   884  type Destination interface {
   885  	io.Writer
   886  	Delete() error
   887  }
   888  
   889  // Download downloads the given target file from remote storage into dest.
   890  //
   891  // dest will be deleted and an error returned in the following situations:
   892  //
   893  //   - The target does not exist in the local targets.json
   894  //   - Failed to fetch the chain of delegations accessible from local snapshot.json
   895  //   - The target does not exist in any targets
   896  //   - Metadata cannot be generated for the downloaded data
   897  //   - Generated metadata does not match local metadata for the given file
   898  //   - Size of the download does not match if the reported size is known and
   899  //     incorrect
   900  func (c *Client) Download(name string, dest Destination) (err error) {
   901  	// delete dest if there is an error
   902  	defer func() {
   903  		if err != nil {
   904  			dest.Delete()
   905  		}
   906  	}()
   907  
   908  	// populate c.targets from local storage if not set
   909  	if c.targets == nil {
   910  		if err := c.getLocalMeta(); err != nil {
   911  			return err
   912  		}
   913  	}
   914  
   915  	normalizedName := util.NormalizeTarget(name)
   916  	localMeta, ok := c.targets[normalizedName]
   917  	if !ok {
   918  		// search in delegations
   919  		localMeta, err = c.getTargetFileMeta(normalizedName)
   920  		if err != nil {
   921  			return err
   922  		}
   923  	}
   924  
   925  	// get the data from remote storage
   926  	r, size, err := c.downloadTarget(normalizedName, c.remote.GetTarget, localMeta.Hashes)
   927  	if err != nil {
   928  		return err
   929  	}
   930  	defer r.Close()
   931  
   932  	// return ErrWrongSize if the reported size is known and incorrect
   933  	if size >= 0 && size != localMeta.Length {
   934  		return ErrWrongSize{name, size, localMeta.Length}
   935  	}
   936  
   937  	// wrap the data in a LimitReader so we download at most localMeta.Length bytes
   938  	stream := io.LimitReader(r, localMeta.Length)
   939  
   940  	// read the data, simultaneously writing it to dest and generating metadata
   941  	actual, err := util.GenerateTargetFileMeta(io.TeeReader(stream, dest), localMeta.HashAlgorithms()...)
   942  	if err != nil {
   943  		return ErrDownloadFailed{name, err}
   944  	}
   945  
   946  	// check the data has the correct length and hashes
   947  	if err := util.TargetFileMetaEqual(actual, localMeta); err != nil {
   948  		if e, ok := err.(util.ErrWrongLength); ok {
   949  			return ErrWrongSize{name, e.Actual, e.Expected}
   950  		}
   951  		return ErrDownloadFailed{name, err}
   952  	}
   953  
   954  	return nil
   955  }
   956  
   957  func (c *Client) VerifyDigest(digest string, digestAlg string, length int64, path string) error {
   958  	localMeta, ok := c.targets[path]
   959  	if !ok {
   960  		return ErrUnknownTarget{Name: path, SnapshotVersion: c.snapshotVer}
   961  	}
   962  
   963  	actual := data.FileMeta{Length: length, Hashes: make(data.Hashes, 1)}
   964  	var err error
   965  	actual.Hashes[digestAlg], err = hex.DecodeString(digest)
   966  	if err != nil {
   967  		return err
   968  	}
   969  
   970  	if err := util.TargetFileMetaEqual(data.TargetFileMeta{FileMeta: actual}, localMeta); err != nil {
   971  		if e, ok := err.(util.ErrWrongLength); ok {
   972  			return ErrWrongSize{path, e.Actual, e.Expected}
   973  		}
   974  		return ErrDownloadFailed{path, err}
   975  	}
   976  
   977  	return nil
   978  }
   979  
   980  // Target returns the target metadata for a specific target if it
   981  // exists, searching from top-level level targets then through
   982  // all delegations. If it does not, ErrNotFound will be returned.
   983  func (c *Client) Target(name string) (data.TargetFileMeta, error) {
   984  	target, err := c.getTargetFileMeta(util.NormalizeTarget(name))
   985  	if err == nil {
   986  		return target, nil
   987  	}
   988  
   989  	if _, ok := err.(ErrUnknownTarget); ok {
   990  		return data.TargetFileMeta{}, ErrNotFound{name}
   991  	}
   992  
   993  	return data.TargetFileMeta{}, err
   994  }
   995  
   996  // Targets returns the complete list of available top-level targets.
   997  func (c *Client) Targets() (data.TargetFiles, error) {
   998  	// populate c.targets from local storage if not set
   999  	if c.targets == nil {
  1000  		if err := c.getLocalMeta(); err != nil {
  1001  			return nil, err
  1002  		}
  1003  	}
  1004  	return c.targets, nil
  1005  }
  1006  

View as plain text