Repository: theupdateframework/go-tuf
Pull request #162 (2021-10-05 12:04:42)

add custom fields to all top level roles
### What does this PR do ?

We are looking to add custom metadata to `root` and `targets` roles. I'm also adding the same custom filed to `snapshot` and `timestamp` to align all the roles nicely.

Relevant entry in the specification: 
> 4. Document formats
>All of the formats described below include the ability to add more attribute-value fields to objects for backwards-compatible format changes. Implementers who encounter undefined attribute-value pairs in the format must include the data when calculating hashes or verifying signatures and must preserve the data when re-serializing. If a backwards incompatible format change is needed, a new filename can be used.

https://theupdateframework.github.io/specification/latest/#document-formats



coveralls: ## Pull Request Test Coverage Report for [Build 1307725379](https://coveralls.io/builds/43293855)

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-2.0%**) to **68.023%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/43293855/badge)](https://coveralls.io/builds/43293855) |
| :-- | --: |
| Change from base [Build 1287560594](https://coveralls.io/builds/43155793): |  -2.0% |
| Covered Lines: | 1872 |
| Relevant Lines: | 2752 |

---
##### 💛  - [Coveralls](https://coveralls.io)

hosseinsia: Can we add a minimum test to make sure this is working?

Patch:
diff --git a/data/types.go b/data/types.go
index 01a811f..700fa15 100644
--- a/data/types.go
+++ b/data/types.go
@@ -4,12 +4,13 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"encoding/json"
+	"errors"
+	"fmt"
 	"path/filepath"
 	"strings"
 	"sync"
 	"time"
 
-	"github.com/pkg/errors"
 	cjson "github.com/tent/canonical-json-go"
 )
 
@@ -59,7 +60,7 @@ func (k *PublicKey) IDs() []string {
 	k.idOnce.Do(func() {
 		data, err := cjson.Marshal(k)
 		if err != nil {
-			panic(errors.Wrap(err, "tuf: error creating key ID"))
+			panic(fmt.Errorf("tuf: error creating key ID: %w", err))
 		}
 		digest := sha256.Sum256(data)
 		k.ids = []string{hex.EncodeToString(digest[:])}
@@ -98,6 +99,7 @@ type Root struct {
 	Expires     time.Time             `json:"expires"`
 	Keys        map[string]*PublicKey `json:"keys"`
 	Roles       map[string]*Role      `json:"roles"`
+	Custom      *json.RawMessage      `json:"custom,omitempty"`
 
 	ConsistentSnapshot bool `json:"consistent_snapshot"`
 }
@@ -170,11 +172,12 @@ type SnapshotFileMeta struct {
 type SnapshotFiles map[string]SnapshotFileMeta
 
 type Snapshot struct {
-	Type        string        `json:"_type"`
-	SpecVersion string        `json:"spec_version"`
-	Version     int           `json:"version"`
-	Expires     time.Time     `json:"expires"`
-	Meta        SnapshotFiles `json:"meta"`
+	Type        string           `json:"_type"`
+	SpecVersion string           `json:"spec_version"`
+	Version     int              `json:"version"`
+	Expires     time.Time        `json:"expires"`
+	Meta        SnapshotFiles    `json:"meta"`
+	Custom      *json.RawMessage `json:"custom,omitempty"`
 }
 
 func NewSnapshot() *Snapshot {
@@ -197,12 +200,13 @@ func (f TargetFileMeta) HashAlgorithms() []string {
 }
 
 type Targets struct {
-	Type        string       `json:"_type"`
-	SpecVersion string       `json:"spec_version"`
-	Version     int          `json:"version"`
-	Expires     time.Time    `json:"expires"`
-	Targets     TargetFiles  `json:"targets"`
-	Delegations *Delegations `json:"delegations,omitempty"`
+	Type        string           `json:"_type"`
+	SpecVersion string           `json:"spec_version"`
+	Version     int              `json:"version"`
+	Expires     time.Time        `json:"expires"`
+	Targets     TargetFiles      `json:"targets"`
+	Delegations *Delegations     `json:"delegations,omitempty"`
+	Custom      *json.RawMessage `json:"custom,omitempty"`
 }
 
 // Delegations represents the edges from a parent Targets role to one or more
@@ -303,11 +307,12 @@ type TimestampFileMeta struct {
 type TimestampFiles map[string]TimestampFileMeta
 
 type Timestamp struct {
-	Type        string         `json:"_type"`
-	SpecVersion string         `json:"spec_version"`
-	Version     int            `json:"version"`
-	Expires     time.Time      `json:"expires"`
-	Meta        TimestampFiles `json:"meta"`
+	Type        string           `json:"_type"`
+	SpecVersion string           `json:"spec_version"`
+	Version     int              `json:"version"`
+	Expires     time.Time        `json:"expires"`
+	Meta        TimestampFiles   `json:"meta"`
+	Custom      *json.RawMessage `json:"custom,omitempty"`
 }
 
 func NewTimestamp() *Timestamp {
diff --git a/go.mod b/go.mod
index ccc2031..524aedf 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,7 @@ require (
 	github.com/dustin/go-humanize v1.0.0
 	github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e
 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	github.com/pkg/errors v0.9.1
-	github.com/stretchr/testify v1.7.0 // indirect
+	github.com/stretchr/testify v1.7.0
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613
 	golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d
diff --git a/go.sum b/go.sum
index 8fef189..0adc981 100644
--- a/go.sum
+++ b/go.sum
@@ -4,10 +4,13 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e h1:Ss/B3/5wWRh8+emnK0++g5zQzwDTi30W10pKxKc4JXI=
 github.com/flynn/go-docopt v0.0.0-20140912013429-f6dd2ebbb31e/go.mod h1:HyVoz1Mz5Co8TFO8EupIdlcpwShBmY98dkT2xeHkvEI=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@@ -15,10 +18,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -32,18 +35,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d h1:adrbvkTDn9rGnXg2IJDKozEpXXLZN89pdIA+Syt4/u0=
 golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/keys/ed25519.go b/pkg/keys/ed25519.go
index cd161db..55d91e0 100644
--- a/pkg/keys/ed25519.go
+++ b/pkg/keys/ed25519.go
@@ -5,8 +5,8 @@ import (
 	"crypto/ed25519"
 	"crypto/rand"
 	"encoding/json"
+	"errors"
 
-	"github.com/pkg/errors"
 	"github.com/theupdateframework/go-tuf/data"
 )
 
diff --git a/pkg/keys/keys.go b/pkg/keys/keys.go
index 7fa9f41..702c420 100644
--- a/pkg/keys/keys.go
+++ b/pkg/keys/keys.go
@@ -1,9 +1,10 @@
 package keys
 
 import (
+	"errors"
+	"fmt"
 	"sync"
 
-	"github.com/pkg/errors"
 	"github.com/theupdateframework/go-tuf/data"
 )
 
@@ -60,7 +61,7 @@ func GetVerifier(key *data.PublicKey) (Verifier, error) {
 	}
 	s := st.(func() Verifier)()
 	if err := s.UnmarshalPublicKey(key); err != nil {
-		return nil, errors.Wrap(err, "tuf: error unmarshalling key")
+		return nil, fmt.Errorf("tuf: error unmarshalling key: %w", err)
 	}
 	return s, nil
 }
@@ -72,7 +73,7 @@ func GetSigner(key *data.PrivateKey) (Signer, error) {
 	}
 	s := st.(func() Signer)()
 	if err := s.UnmarshalPrivateKey(key); err != nil {
-		return nil, errors.Wrap(err, "tuf: error unmarshalling key")
+		return nil, fmt.Errorf("tuf: error unmarshalling key: %w", err)
 	}
 	return s, nil
 }
diff --git a/pkg/keys/rsa.go b/pkg/keys/rsa.go
index 72c715b..28c82d1 100644
--- a/pkg/keys/rsa.go
+++ b/pkg/keys/rsa.go
@@ -8,8 +8,7 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"encoding/pem"
-
-	"github.com/pkg/errors"
+	"errors"
 
 	"github.com/theupdateframework/go-tuf/data"
 )


Test patch:
diff --git a/data/types_test.go b/data/types_test.go
index 7bf4595..c7022d6 100644
--- a/data/types_test.go
+++ b/data/types_test.go
@@ -239,3 +239,49 @@ func TestDelegatedRoleUnmarshalErr(t *testing.T) {
 	err := json.Unmarshal([]byte(`{"keyids":"a"}`), &d)
 	assert.Equal(t, "keyids", err.(*json.UnmarshalTypeError).Field)
 }
+
+func TestCustomField(t *testing.T) {
+	testCustomJSON := json.RawMessage([]byte(`{"test":true}`))
+
+	root := Root{
+		Type:               "root",
+		SpecVersion:        "1.0",
+		Keys:               make(map[string]*PublicKey),
+		Roles:              make(map[string]*Role),
+		ConsistentSnapshot: true,
+		Custom:             &testCustomJSON,
+	}
+	rootJSON, err := json.Marshal(&root)
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("{\"_type\":\"root\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"keys\":{},\"roles\":{},\"custom\":{\"test\":true},\"consistent_snapshot\":true}"), rootJSON)
+
+	targets := Targets{
+		Type:        "targets",
+		SpecVersion: "1.0",
+		Targets:     make(TargetFiles),
+		Custom:      &testCustomJSON,
+	}
+	targetsJSON, err := json.Marshal(&targets)
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("{\"_type\":\"targets\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"targets\":{},\"custom\":{\"test\":true}}"), targetsJSON)
+
+	snapshot := Snapshot{
+		Type:        "snapshot",
+		SpecVersion: "1.0",
+		Meta:        make(SnapshotFiles),
+		Custom:      &testCustomJSON,
+	}
+	snapshotJSON, err := json.Marshal(&snapshot)
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("{\"_type\":\"snapshot\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"meta\":{},\"custom\":{\"test\":true}}"), snapshotJSON)
+
+	timestamp := Timestamp{
+		Type:        "timestamp",
+		SpecVersion: "1.0",
+		Meta:        make(TimestampFiles),
+		Custom:      &testCustomJSON,
+	}
+	timestampJSON, err := json.Marshal(&timestamp)
+	assert.NoError(t, err)
+	assert.Equal(t, []byte("{\"_type\":\"timestamp\",\"spec_version\":\"1.0\",\"version\":0,\"expires\":\"0001-01-01T00:00:00Z\",\"meta\":{},\"custom\":{\"test\":true}}"), timestampJSON)
+}