Repository: theupdateframework/go-tuf (go)

Issue:
Support changing keys file passphrase
I should be able to change the passphrase of a keys file, for example:

```
$ tuf change-passphrase root
Enter current root keys passphrase:
Enter new root keys passphrase:
Repeat new root keys passphrase:
```


Pull request description:
Add support for changing keys file passphrase
The following PR adds support for changing the passphrase for a given role keys file. 

Closes #60 

The format of the command is `tuf change-passphrase <role>`. Example usage:

* In case the keys file exist - 

```bash
tuf change-passphrase root
Enter root keys passphrase: 
Enter new root keys passphrase: 
Repeat new root keys passphrase: 
Successfully changed passphrase for root keys file
```

* In case the keys file doesn't exist yet - 

```bash
tuf change-passphrase snapshot
Failed to change passphrase. Missing keys file for snapshot role. 
ERROR: open <path>/go-tuf/cmd/tuf/keys/snapshot.json: no such file or directory
```

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fcaf677..9faec3a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -4,7 +4,7 @@ jobs:
   test:
     strategy:
       matrix:
-        go-version: [1.15.x, 1.16.x, 1.17.x]
+        go-version: [1.16.x, 1.17.x]
         os: [ubuntu-latest, macos-latest]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/.gitignore b/.gitignore
index 761fccd..64b8f8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 .DS_Store
 cmd/tuf/tuf
 cmd/tuf-client/tuf-client
+.vscode
diff --git a/README.md b/README.md
index 4bff66f..f99ff87 100644
--- a/README.md
+++ b/README.md
@@ -122,10 +122,17 @@ Sets `role`'s threshold (required number of keys for signing) to
 
 Outputs `role`'s threshold (required number of keys for signing).
 
+#### `tuf change-passphrase <role>`
+
+Changes the passphrase for given role keys file. The CLI supports reading
+both the existing and the new passphrase via the following environment
+variables - `TUF_{{ROLE}}_PASSPHRASE` and respectively `TUF_NEW_{{ROLE}}_PASSPHRASE`
+
 #### Usage of environment variables
 
 The `tuf` CLI supports receiving passphrases via environment variables in
-the form of `TUF_{{ROLE}}_PASSPHRASE`
+the form of `TUF_{{ROLE}}_PASSPHRASE` for existing ones and
+`TUF_NEW_{{ROLE}}_PASSPHRASE` for setting new ones.
 
 For a list of supported commands, run `tuf help` from the command line.
 
diff --git a/cmd/tuf/change_passphrase.go b/cmd/tuf/change_passphrase.go
new file mode 100644
index 0000000..be685ad
--- /dev/null
+++ b/cmd/tuf/change_passphrase.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"github.com/flynn/go-docopt"
+	"github.com/theupdateframework/go-tuf"
+)
+
+func init() {
+	register("change-passphrase", cmdChangePassphrase, `
+usage: tuf change-passphrase <role>
+
+Changes the passphrase for given role keys file.
+
+Alternatively, passphrases can be passed via environment variables in the
+form of TUF_{{ROLE}}_PASSPHRASE for existing ones and
+TUF_NEW_{{ROLE}}_PASSPHRASE for setting new ones.
+`)
+}
+
+func cmdChangePassphrase(args *docopt.Args, repo *tuf.Repo) error {
+	return repo.ChangePassphrase(args.String["<role>"])
+}
diff --git a/cmd/tuf/main.go b/cmd/tuf/main.go
index a168dc8..a231f51 100644
--- a/cmd/tuf/main.go
+++ b/cmd/tuf/main.go
@@ -28,21 +28,22 @@ Options:
   --insecure-plaintext  Don't encrypt signing keys
 
 Commands:
-  help          Show usage for a specific command
-  init          Initialize a new repository
-  gen-key       Generate a new signing key for a specific metadata file
-  revoke-key    Revoke a signing key
-  add           Add target file(s)
-  remove        Remove a target file
-  snapshot      Update the snapshot metadata file
-  timestamp     Update the timestamp metadata file
-  sign          Sign a role's metadata file
-  commit        Commit staged files to the repository
-  regenerate    Recreate the targets metadata file [Not supported yet]
-  clean         Remove all staged metadata files
-  root-keys     Output a JSON serialized array of root keys to STDOUT
-  set-threshold Sets the threshold for a role
-  get-threshold Outputs the threshold for a role
+  help               Show usage for a specific command
+  init               Initialize a new repository
+  gen-key            Generate a new signing key for a specific metadata file
+  revoke-key         Revoke a signing key
+  add                Add target file(s)
+  remove             Remove a target file
+  snapshot           Update the snapshot metadata file
+  timestamp          Update the timestamp metadata file
+  sign               Sign a role's metadata file
+  commit             Commit staged files to the repository
+  regenerate         Recreate the targets metadata file [Not supported yet]
+  set-threshold      Sets the threshold for a role
+  get-threshold      Outputs the threshold for a role
+  change-passphrase  Changes the passphrase for given role keys file
+  root-keys          Output a JSON serialized array of root keys to STDOUT
+  clean              Remove all staged metadata files
 
 See "tuf help <command>" for more information on a specific command
 `
@@ -125,11 +126,22 @@ func parseExpires(arg string) (time.Time, error) {
 	return time.Now().AddDate(0, 0, days).UTC(), nil
 }
 
-func getPassphrase(role string, confirm bool) ([]byte, error) {
-	if pass := os.Getenv(fmt.Sprintf("TUF_%s_PASSPHRASE", strings.ToUpper(role))); pass != "" {
+func getPassphrase(role string, confirm bool, change bool) ([]byte, error) {
+	// In case of change we need to prompt explicitly for a new passphrase
+	// and not read it from the environment variable, if present
+	if pass := os.Getenv(fmt.Sprintf("TUF_%s_PASSPHRASE", strings.ToUpper(role))); pass != "" && !change {
 		return []byte(pass), nil
 	}
-
+	// Alter role string if we are prompting for a passphrase change
+	if change {
+		// Check if environment variable for new passphrase exist
+		if new_pass := os.Getenv(fmt.Sprintf("TUF_NEW_%s_PASSPHRASE", strings.ToUpper(role))); new_pass != "" {
+			// If so, just read the new passphrase from it and return
+			return []byte(new_pass), nil
+		}
+		// No environment variable set, so proceed prompting for new passphrase
+		role = fmt.Sprintf("new %s", role)
+	}
 	fmt.Printf("Enter %s keys passphrase: ", role)
 	passphrase, err := terminal.ReadPassword(int(syscall.Stdin))
 	fmt.Println()
diff --git a/data/types.go b/data/types.go
index 3083c11..125590c 100644
--- a/data/types.go
+++ b/data/types.go
@@ -11,7 +11,7 @@ import (
 	"sync"
 	"time"
 
-	cjson "github.com/tent/canonical-json-go"
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 )
 
 const (
@@ -58,7 +58,7 @@ type PrivateKey struct {
 
 func (k *PublicKey) IDs() []string {
 	k.idOnce.Do(func() {
-		data, err := cjson.Marshal(k)
+		data, err := cjson.EncodeCanonical(k)
 		if err != nil {
 			panic(fmt.Errorf("tuf: error creating key ID: %w", err))
 		}
diff --git a/errors.go b/errors.go
index c471dc4..33d67c4 100644
--- a/errors.go
+++ b/errors.go
@@ -7,8 +7,9 @@ import (
 )
 
 var (
-	ErrInitNotAllowed = errors.New("tuf: repository already initialized")
-	ErrNewRepository  = errors.New("tuf: repository not yet committed")
+	ErrInitNotAllowed               = errors.New("tuf: repository already initialized")
+	ErrNewRepository                = errors.New("tuf: repository not yet committed")
+	ErrChangePassphraseNotSupported = errors.New("tuf: store does not support changing passphrase")
 )
 
 type ErrMissingMetadata struct {
diff --git a/go.mod b/go.mod
index 524aedf..a89268b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,14 +1,13 @@
 module github.com/theupdateframework/go-tuf
 
-go 1.14
+go 1.16
 
 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/secure-systems-lab/go-securesystemslib v0.2.0
 	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
-	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
+	golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
+	gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
 )
diff --git a/go.sum b/go.sum
index 0adc981..fc8db10 100644
--- a/go.sum
+++ b/go.sum
@@ -1,22 +1,16 @@
+github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 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=
-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=
@@ -24,30 +18,32 @@ 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/secure-systems-lab/go-securesystemslib v0.2.0 h1:9beLHgmhA2KEqJkFh1bs/YlnHkazv26GCXqfcUdC1YI=
+github.com/secure-systems-lab/go-securesystemslib v0.2.0/go.mod h1:eIjBmIP8LD2MLBL/DkQWayLiz006Q4p+hCu79rvWleY=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 h1:iGnD/q9160NWqKZZ5vY4p0dMiYMRknzctfSkqA4nBDw=
-github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613/go.mod h1:g6AnIpDSYMcphz193otpSIzN+11Rs+AAIIC6rm1enug=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-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/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
+golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 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/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 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=
diff --git a/local_store.go b/local_store.go
index bc92652..320a848 100644
--- a/local_store.go
+++ b/local_store.go
@@ -3,6 +3,7 @@ package tuf
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
@@ -331,6 +332,44 @@ func (f *fileSystemStore) GetSigners(role string) ([]keys.Signer, error) {
 	return f.signers[role], nil
 }
 
+// ChangePassphrase changes the passphrase for a role keys file. Implements
+// PassphraseChanger interface.
+func (f *fileSystemStore) ChangePassphrase(role string) error {
+	// No need to proceed if passphrase func is not set
+	if f.passphraseFunc == nil {
+		return ErrPassphraseRequired{role}
+	}
+	// Read the existing keys (if any)
+	// If encrypted, will prompt for existing passphrase
+	keys, _, err := f.loadPrivateKeys(role)
+	if err != nil {
+		if os.IsNotExist(err) {
+			fmt.Printf("Failed to change passphrase. Missing keys file for %s role. \n", role)
+		}
+		return err
+	}
+	// Prompt for new passphrase
+	pass, err := f.passphraseFunc(role, true, true)
+	if err != nil {
+		return err
+	}
+	// Proceed saving the keys
+	pk := &persistedKeys{Encrypted: true}
+	pk.Data, err = encrypted.Marshal(keys, pass)
+	if err != nil {
+		return err
+	}
+	data, err := json.MarshalIndent(pk, "", "\t")
+	if err != nil {
+		return err
+	}
+	if err := util.AtomicallyWriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
+		return err
+	}
+	fmt.Printf("Successfully changed passphrase for %s keys file\n", role)
+	return nil
+}
+
 func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
 	if err := f.createDirs(); err != nil {
 		return err
@@ -352,7 +391,7 @@ func (f *fileSystemStore) SaveSigner(role string, signer keys.Signer) error {
 	// be encrypted later (passphraseFunc being nil indicates the keys file
 	// should not be encrypted)
 	if pass == nil && f.passphraseFunc != nil {
-		pass, err = f.passphraseFunc(role, true)
+		pass, err = f.passphraseFunc(role, true, false)
 		if err != nil {
 			return err
 		}
@@ -412,7 +451,7 @@ func (f *fileSystemStore) loadPrivateKeys(role string) ([]*data.PrivateKey, []by
 	// try the empty string as the password first
 	pass := []byte("")
 	if err := encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
-		pass, err = f.passphraseFunc(role, false)
+		pass, err = f.passphraseFunc(role, false, false)
 		if err != nil {
 			return nil, nil, err
 		}
diff --git a/repo.go b/repo.go
index fcc6e8d..1cbe63e 100644
--- a/repo.go
+++ b/repo.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 	"time"
 
-	cjson "github.com/tent/canonical-json-go"
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/theupdateframework/go-tuf/data"
 	"github.com/theupdateframework/go-tuf/internal/signer"
 	"github.com/theupdateframework/go-tuf/pkg/keys"
@@ -55,13 +55,18 @@ type LocalStore interface {
 	// GetSigners return a list of signers for a role.
 	GetSigners(string) ([]keys.Signer, error)
 
-	// SavePrivateKey adds a signer to a role.
+	// SaveSigner adds a signer to a role.
 	SaveSigner(string, keys.Signer) error
 
 	// Clean is used to remove all staged metadata files.
 	Clean() error
 }
 
+type PassphraseChanger interface {
+	// ChangePassphrase changes the passphrase for a role keys file.
+	ChangePassphrase(string) error
+}
+
 type Repo struct {
 	local          LocalStore
 	hashAlgorithms []string
@@ -313,6 +318,18 @@ func (r *Repo) timestamp() (*data.Timestamp, error) {
 	return timestamp, nil
 }
 
+func (r *Repo) ChangePassphrase(keyRole string) error {
+	if !verify.ValidRole(keyRole) {
+		return ErrInvalidRole{keyRole}
+	}
+
+	if p, ok := r.local.(PassphraseChanger); ok {
+		return p.ChangePassphrase(keyRole)
+	}
+
+	return ErrChangePassphraseNotSupported
+}
+
 func (r *Repo) GenKey(role string) ([]string, error) {
 	return r.GenKeyWithExpires(role, data.DefaultExpires("root"))
 }
@@ -502,7 +519,7 @@ func (r *Repo) RevokeKeyWithExpires(keyRole, id string, expires time.Time) error
 }
 
 func (r *Repo) jsonMarshal(v interface{}) ([]byte, error) {
-	b, err := cjson.Marshal(v)
+	b, err := cjson.EncodeCanonical(v)
 	if err != nil {
 		return []byte{}, err
 	}
diff --git a/sign/sign.go b/sign/sign.go
index 3dd8126..06886b5 100644
--- a/sign/sign.go
+++ b/sign/sign.go
@@ -1,7 +1,7 @@
 package sign
 
 import (
-	cjson "github.com/tent/canonical-json-go"
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/theupdateframework/go-tuf/data"
 	"github.com/theupdateframework/go-tuf/pkg/keys"
 )
@@ -39,7 +39,7 @@ func Sign(s *data.Signed, k keys.Signer) error {
 }
 
 func Marshal(v interface{}, keys ...keys.Signer) (*data.Signed, error) {
-	b, err := cjson.Marshal(v)
+	b, err := cjson.EncodeCanonical(v)
 	if err != nil {
 		return nil, err
 	}
diff --git a/util/util.go b/util/util.go
index 751c677..b329111 100644
--- a/util/util.go
+++ b/util/util.go
@@ -72,7 +72,7 @@ func (e ErrUnknownHashAlgorithm) Error() string {
 	return fmt.Sprintf("unknown hash algorithm: %s", e.Name)
 }
 
-type PassphraseFunc func(role string, confirm bool) ([]byte, error)
+type PassphraseFunc func(role string, confirm bool, change bool) ([]byte, error)
 
 func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error {
 	if actual.Length != expected.Length {
diff --git a/verify/verify.go b/verify/verify.go
index c0b6a03..909034c 100644
--- a/verify/verify.go
+++ b/verify/verify.go
@@ -5,7 +5,7 @@ import (
 	"strings"
 	"time"
 
-	cjson "github.com/tent/canonical-json-go"
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/theupdateframework/go-tuf/data"
 )
 
@@ -83,7 +83,7 @@ func (db *DB) VerifySignatures(s *data.Signed, role string) error {
 	if err := json.Unmarshal(s.Signed, &decoded); err != nil {
 		return err
 	}
-	msg, err := cjson.Marshal(decoded)
+	msg, err := cjson.EncodeCanonical(decoded)
 	if err != nil {
 		return err
 	}


Test patch:
diff --git a/client/client_test.go b/client/client_test.go
index fb4ee32..38d3488 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -14,8 +14,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/stretchr/testify/assert"
-	cjson "github.com/tent/canonical-json-go"
 	tuf "github.com/theupdateframework/go-tuf"
 	"github.com/theupdateframework/go-tuf/data"
 	"github.com/theupdateframework/go-tuf/pkg/keys"
@@ -1126,7 +1126,7 @@ func (s *ClientSuite) TestUnknownKeyIDs(c *C) {
 	signedRoot, err := sign.Marshal(root.Signed, signingKeys...)
 	c.Assert(err, IsNil)
 
-	rootJSON, err = cjson.Marshal(signedRoot)
+	rootJSON, err = cjson.EncodeCanonical(signedRoot)
 	c.Assert(err, IsNil)
 
 	s.store.SetMeta("root.json", rootJSON)
diff --git a/data/types_test.go b/data/types_test.go
index c7022d6..b9523fa 100644
--- a/data/types_test.go
+++ b/data/types_test.go
@@ -4,8 +4,8 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/stretchr/testify/assert"
-	cjson "github.com/tent/canonical-json-go"
 	. "gopkg.in/check.v1"
 )
 
@@ -218,7 +218,7 @@ func TestDelegatedRoleJSON(t *testing.T) {
 
 	for _, tt := range tts {
 		t.Run(tt.testName, func(t *testing.T) {
-			b, err := cjson.Marshal(tt.d)
+			b, err := cjson.EncodeCanonical(tt.d)
 			assert.NoError(t, err)
 			assert.Equal(t, tt.rawCJSON, string(b))
 
diff --git a/repo_test.go b/repo_test.go
index ab4877a..1ed0be5 100644
--- a/repo_test.go
+++ b/repo_test.go
@@ -15,7 +15,7 @@ import (
 	"testing"
 	"time"
 
-	cjson "github.com/tent/canonical-json-go"
+	"github.com/secure-systems-lab/go-securesystemslib/cjson"
 	"github.com/theupdateframework/go-tuf/data"
 	"github.com/theupdateframework/go-tuf/encrypted"
 	"github.com/theupdateframework/go-tuf/pkg/keys"
@@ -1255,14 +1255,20 @@ func (rs *RepoSuite) TestHashAlgorithm(c *C) {
 	}
 }
 
-func testPassphraseFunc(p []byte) util.PassphraseFunc {
-	return func(string, bool) ([]byte, error) { return p, nil }
-}
-
 func (rs *RepoSuite) TestKeyPersistence(c *C) {
 	tmp := newTmpDir(c)
-	passphrase := []byte("s3cr3t")
-	store := FileSystemStore(tmp.path, testPassphraseFunc(passphrase))
+	oldPassphrase := []byte("old_s3cr3t")
+	newPassphrase := []byte("new_s3cr3t")
+	// returnNewPassphrase is used to force the passphrase function to return the new passphrase when called by the SaveSigner() method
+	returnNewPassphrase := false
+	// passphrase mock function
+	testPassphraseFunc := func(a string, b, change bool) ([]byte, error) {
+		if change || returnNewPassphrase {
+			return newPassphrase, nil
+		}
+		return oldPassphrase, nil
+	}
+	store := FileSystemStore(tmp.path, testPassphraseFunc)
 
 	assertKeys := func(role string, enc bool, expected []*data.PrivateKey) {
 		keysJSON := tmp.readFile("keys/" + role + ".json")
@@ -1271,9 +1277,13 @@ func (rs *RepoSuite) TestKeyPersistence(c *C) {
 
 		// check the persisted keys are correct
 		var actual []*data.PrivateKey
+		pass := oldPassphrase
 		if enc {
 			c.Assert(pk.Encrypted, Equals, true)
-			decrypted, err := encrypted.Decrypt(pk.Data, passphrase)
+			if returnNewPassphrase {
+				pass = newPassphrase
+			}
+			decrypted, err := encrypted.Decrypt(pk.Data, pass)
 			c.Assert(err, IsNil)
 			c.Assert(json.Unmarshal(decrypted, &actual), IsNil)
 		} else {
@@ -1293,10 +1303,10 @@ func (rs *RepoSuite) TestKeyPersistence(c *C) {
 
 			numMatches := 0
 			for _, wantKey := range expected {
-				wantCanon, err := cjson.Marshal(wantKey)
+				wantCanon, err := cjson.EncodeCanonical(wantKey)
 				c.Assert(err, IsNil)
 
-				gotCanon, err := cjson.Marshal(gotKey)
+				gotCanon, err := cjson.EncodeCanonical(gotKey)
 				c.Assert(err, IsNil)
 
 				if string(wantCanon) == string(gotCanon) {
@@ -1364,6 +1374,45 @@ func (rs *RepoSuite) TestKeyPersistence(c *C) {
 	c.Assert(err, IsNil)
 	c.Assert(insecureStore.SaveSigner("targets", signer), IsNil)
 	assertKeys("targets", false, []*data.PrivateKey{privateKey})
+
+	// Test changing the passphrase
+	// 1. Create a secure store with a passphrase (create new object and temp folder so we discard any previous state)
+	tmp = newTmpDir(c)
+	store = FileSystemStore(tmp.path, testPassphraseFunc)
+
+	// 2. Test changing the passphrase when the keys file does not exist - should FAIL
+	c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), NotNil)
+
+	// 3. Generate a new key
+	signer, err = keys.GenerateEd25519Key()
+	c.Assert(err, IsNil)
+	privateKey, err = signer.MarshalPrivateKey()
+	c.Assert(err, IsNil)
+	c.Assert(store.SaveSigner("root", signer), IsNil)
+
+	// 4. Verify the key file can be decrypted using the original passphrase - should SUCCEED
+	assertKeys("root", true, []*data.PrivateKey{privateKey})
+
+	// 5. Change the passphrase (our mock passphrase function is called with change=true thus returning the newPassphrase value)
+	c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), IsNil)
+
+	// 6. Try to add a key and implicitly decrypt the keys file using the OLD passphrase - should FAIL
+	newKey, err = keys.GenerateEd25519Key()
+	c.Assert(err, IsNil)
+	newPrivateKey, err = newKey.MarshalPrivateKey()
+	c.Assert(err, IsNil)
+	c.Assert(store.SaveSigner("root", newKey), NotNil)
+
+	// 7. Try to add a key and implicitly decrypt the keys using the NEW passphrase - should SUCCEED
+	returnNewPassphrase = true
+	newKey, err = keys.GenerateEd25519Key()
+	c.Assert(err, IsNil)
+	newPrivateKey, err = newKey.MarshalPrivateKey()
+	c.Assert(err, IsNil)
+	c.Assert(store.SaveSigner("root", newKey), IsNil)
+
+	// 8. Verify again that the key entries are what we expect after decrypting them using the NEW passphrase
+	assertKeys("root", true, []*data.PrivateKey{privateKey, newPrivateKey})
 }
 
 func (rs *RepoSuite) TestManageMultipleTargets(c *C) {