Ver Fonte

Merge pull request #27074 from cyli/bump-notary-version

Bump notary version to 0.4.2
Vincent Demeester há 8 anos atrás
pai
commit
a7c883fb04
87 ficheiros alterados com 2455 adições e 2421 exclusões
  1. 1 1
      Dockerfile
  2. 1 1
      Dockerfile.aarch64
  3. 1 1
      Dockerfile.armhf
  4. 1 1
      Dockerfile.ppc64le
  5. 1 1
      Dockerfile.s390x
  6. 7 6
      cli/command/image/trust.go
  7. 1 1
      hack/vendor.sh
  8. 4 4
      integration-cli/docker_cli_create_test.go
  9. 4 4
      integration-cli/docker_cli_pull_trusted_test.go
  10. 5 5
      integration-cli/docker_cli_run_test.go
  11. 19 22
      integration-cli/fixtures/notary/delgkey1.crt
  12. 25 25
      integration-cli/fixtures/notary/delgkey1.key
  13. 19 22
      integration-cli/fixtures/notary/delgkey2.crt
  14. 25 25
      integration-cli/fixtures/notary/delgkey2.key
  15. 19 22
      integration-cli/fixtures/notary/delgkey3.crt
  16. 25 25
      integration-cli/fixtures/notary/delgkey3.key
  17. 19 22
      integration-cli/fixtures/notary/delgkey4.crt
  18. 25 25
      integration-cli/fixtures/notary/delgkey4.key
  19. 18 0
      integration-cli/fixtures/notary/gen.sh
  20. 2 0
      vendor/src/github.com/docker/notary/.gitignore
  21. 61 0
      vendor/src/github.com/docker/notary/CHANGELOG.md
  22. 7 4
      vendor/src/github.com/docker/notary/Dockerfile
  23. 34 48
      vendor/src/github.com/docker/notary/Makefile
  24. 1 1
      vendor/src/github.com/docker/notary/NOTARY_VERSION
  25. 3 2
      vendor/src/github.com/docker/notary/README.md
  26. 6 70
      vendor/src/github.com/docker/notary/circle.yml
  27. 19 18
      vendor/src/github.com/docker/notary/client/changelist/change.go
  28. 18 0
      vendor/src/github.com/docker/notary/client/changelist/changelist.go
  29. 30 9
      vendor/src/github.com/docker/notary/client/changelist/file_changelist.go
  30. 3 0
      vendor/src/github.com/docker/notary/client/changelist/interface.go
  31. 131 44
      vendor/src/github.com/docker/notary/client/client.go
  32. 25 20
      vendor/src/github.com/docker/notary/client/delegations.go
  33. 63 22
      vendor/src/github.com/docker/notary/client/helpers.go
  34. 2 2
      vendor/src/github.com/docker/notary/client/repo.go
  35. 2 2
      vendor/src/github.com/docker/notary/client/repo_pkcs11.go
  36. 54 44
      vendor/src/github.com/docker/notary/client/tufclient.go
  37. 69 0
      vendor/src/github.com/docker/notary/client/witness.go
  38. 4 0
      vendor/src/github.com/docker/notary/codecov.yml
  39. 5 3
      vendor/src/github.com/docker/notary/const.go
  40. 16 0
      vendor/src/github.com/docker/notary/const_nowindows.go
  41. 8 0
      vendor/src/github.com/docker/notary/const_windows.go
  42. 0 10
      vendor/src/github.com/docker/notary/coverpkg.sh
  43. 2 2
      vendor/src/github.com/docker/notary/cryptoservice/certificate.go
  44. 31 5
      vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go
  45. 0 313
      vendor/src/github.com/docker/notary/cryptoservice/import_export.go
  46. 60 0
      vendor/src/github.com/docker/notary/development.mysql.yml
  47. 10 13
      vendor/src/github.com/docker/notary/development.rethink.yml
  48. 0 36
      vendor/src/github.com/docker/notary/development.yml
  49. 15 21
      vendor/src/github.com/docker/notary/docker-compose.rethink.yml
  50. 49 34
      vendor/src/github.com/docker/notary/docker-compose.yml
  51. 7 0
      vendor/src/github.com/docker/notary/notary.go
  52. 125 121
      vendor/src/github.com/docker/notary/passphrase/passphrase.go
  53. 2 1
      vendor/src/github.com/docker/notary/server.Dockerfile
  54. 2 3
      vendor/src/github.com/docker/notary/signer.Dockerfile
  55. 11 2
      vendor/src/github.com/docker/notary/storage/errors.go
  56. 222 0
      vendor/src/github.com/docker/notary/storage/filestore.go
  57. 70 28
      vendor/src/github.com/docker/notary/storage/httpstore.go
  58. 13 10
      vendor/src/github.com/docker/notary/storage/interfaces.go
  59. 46 29
      vendor/src/github.com/docker/notary/storage/memorystore.go
  60. 16 15
      vendor/src/github.com/docker/notary/storage/offlinestore.go
  61. 0 150
      vendor/src/github.com/docker/notary/trustmanager/filestore.go
  62. 82 0
      vendor/src/github.com/docker/notary/trustmanager/interfaces.go
  63. 0 497
      vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go
  64. 305 39
      vendor/src/github.com/docker/notary/trustmanager/keystore.go
  65. 0 67
      vendor/src/github.com/docker/notary/trustmanager/memorystore.go
  66. 0 42
      vendor/src/github.com/docker/notary/trustmanager/store.go
  67. 57 0
      vendor/src/github.com/docker/notary/trustmanager/yubikey/import.go
  68. 17 23
      vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go
  69. 37 0
      vendor/src/github.com/docker/notary/trustpinning/ca.crt
  70. 60 37
      vendor/src/github.com/docker/notary/trustpinning/certs.go
  71. 31 0
      vendor/src/github.com/docker/notary/trustpinning/test.crt
  72. 16 9
      vendor/src/github.com/docker/notary/trustpinning/trustpin.go
  73. 1 31
      vendor/src/github.com/docker/notary/tuf/README.md
  74. 54 17
      vendor/src/github.com/docker/notary/tuf/builder.go
  75. 0 14
      vendor/src/github.com/docker/notary/tuf/client/errors.go
  76. 9 0
      vendor/src/github.com/docker/notary/tuf/data/errors.go
  77. 25 0
      vendor/src/github.com/docker/notary/tuf/data/roles.go
  78. 4 1
      vendor/src/github.com/docker/notary/tuf/data/targets.go
  79. 35 0
      vendor/src/github.com/docker/notary/tuf/data/types.go
  80. 6 2
      vendor/src/github.com/docker/notary/tuf/signed/ed25519.go
  81. 9 4
      vendor/src/github.com/docker/notary/tuf/signed/errors.go
  82. 1 1
      vendor/src/github.com/docker/notary/tuf/signed/sign.go
  83. 9 4
      vendor/src/github.com/docker/notary/tuf/signed/verify.go
  84. 0 102
      vendor/src/github.com/docker/notary/tuf/store/filestore.go
  85. 97 18
      vendor/src/github.com/docker/notary/tuf/tuf.go
  86. 0 109
      vendor/src/github.com/docker/notary/tuf/utils/util.go
  87. 136 109
      vendor/src/github.com/docker/notary/tuf/utils/x509.go

+ 1 - 1
Dockerfile

@@ -176,7 +176,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary and notary-server
 # Install notary and notary-server
-ENV NOTARY_VERSION v0.3.0
+ENV NOTARY_VERSION v0.4.2
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 1 - 1
Dockerfile.aarch64

@@ -121,7 +121,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary and notary-server
 # Install notary and notary-server
-ENV NOTARY_VERSION v0.3.0
+ENV NOTARY_VERSION v0.4.2
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 1 - 1
Dockerfile.armhf

@@ -120,7 +120,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary and notary-server
 # Install notary and notary-server
-ENV NOTARY_VERSION v0.3.0
+ENV NOTARY_VERSION v0.4.2
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 1 - 1
Dockerfile.ppc64le

@@ -139,7 +139,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary and notary-server
 # Install notary and notary-server
-ENV NOTARY_VERSION v0.3.0
+ENV NOTARY_VERSION v0.4.2
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 1 - 1
Dockerfile.s390x

@@ -131,7 +131,7 @@ RUN set -x \
 	&& rm -rf "$GOPATH"
 	&& rm -rf "$GOPATH"
 
 
 # Install notary and notary-server
 # Install notary and notary-server
-ENV NOTARY_VERSION v0.3.0
+ENV NOTARY_VERSION v0.4.2
 RUN set -x \
 RUN set -x \
 	&& export GOPATH="$(mktemp -d)" \
 	&& export GOPATH="$(mktemp -d)" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

+ 7 - 6
cli/command/image/trust.go

@@ -30,13 +30,14 @@ import (
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/docker/go-connections/tlsconfig"
 	"github.com/docker/go-connections/tlsconfig"
+	"github.com/docker/notary"
 	"github.com/docker/notary/client"
 	"github.com/docker/notary/client"
 	"github.com/docker/notary/passphrase"
 	"github.com/docker/notary/passphrase"
+	"github.com/docker/notary/storage"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/store"
 )
 )
 
 
 var (
 var (
@@ -144,7 +145,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 		}
 		}
 
 
 		// Initialize the notary repository with a remotely managed snapshot key
 		// Initialize the notary repository with a remotely managed snapshot key
-		if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
+		if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
 			return notaryError(repoInfo.FullName(), err)
 			return notaryError(repoInfo.FullName(), err)
 		}
 		}
 		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
 		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
@@ -464,7 +465,7 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
 		trustpinning.TrustPinConfig{})
 		trustpinning.TrustPinConfig{})
 }
 }
 
 
-func getPassphraseRetriever(streams command.Streams) passphrase.Retriever {
+func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
 	aliasMap := map[string]string{
 	aliasMap := map[string]string{
 		"root":     "root",
 		"root":     "root",
 		"snapshot": "repository",
 		"snapshot": "repository",
@@ -554,11 +555,11 @@ func notaryError(repoName string, err error) error {
 		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
 		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
 	case trustmanager.ErrKeyNotFound:
 	case trustmanager.ErrKeyNotFound:
 		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
 		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
-	case *net.OpError:
+	case storage.NetworkError:
 		return fmt.Errorf("Error: error contacting notary server: %v", err)
 		return fmt.Errorf("Error: error contacting notary server: %v", err)
-	case store.ErrMetaNotFound:
+	case storage.ErrMetaNotFound:
 		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
 		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
-	case signed.ErrInvalidKeyType:
+	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
 		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
 		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
 	case signed.ErrNoKeys:
 	case signed.ErrNoKeys:
 		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
 		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)

+ 1 - 1
hack/vendor.sh

@@ -99,7 +99,7 @@ clone git github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa
 clone git github.com/pborman/uuid v1.0
 clone git github.com/pborman/uuid v1.0
 
 
 # get desired notary commit, might also need to be updated in Dockerfile
 # get desired notary commit, might also need to be updated in Dockerfile
-clone git github.com/docker/notary v0.3.0
+clone git github.com/docker/notary v0.4.2
 
 
 clone git google.golang.org/grpc v1.0.1-GA https://github.com/grpc/grpc-go.git
 clone git google.golang.org/grpc v1.0.1-GA https://github.com/grpc/grpc-go.git
 clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
 clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f

+ 4 - 4
integration-cli/docker_cli_create_test.go

@@ -416,14 +416,14 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out))
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out))
 
 
-	// Now, try creating with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
+	// Now, try creating with the original client from this new trust server. This should fail because the new root is invalid.
 	createCmd = exec.Command(dockerBinary, "create", repoName)
 	createCmd = exec.Command(dockerBinary, "create", repoName)
 	s.trustedCmd(createCmd)
 	s.trustedCmd(createCmd)
 	out, _, err = runCommandWithOutput(createCmd)
 	out, _, err = runCommandWithOutput(createCmd)
-	if err != nil {
-		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
+	if err == nil {
+		c.Fatalf("Continuing with cached data even though it's an invalid root rotation: %s\n%s", err, out)
 	}
 	}
-	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
+	if !strings.Contains(out, "could not rotate trust to a new trusted root") {
 		c.Fatalf("Missing expected output on trusted create:\n%s", out)
 		c.Fatalf("Missing expected output on trusted create:\n%s", out)
 	}
 	}
 
 

+ 4 - 4
integration-cli/docker_cli_pull_trusted_test.go

@@ -135,14 +135,14 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) {
 	c.Assert(err, check.IsNil, check.Commentf(out))
 	c.Assert(err, check.IsNil, check.Commentf(out))
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out))
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out))
 
 
-	// Now, try pulling with the original client from this new trust server. This should fall back to cached metadata.
+	// Now, try pulling with the original client from this new trust server. This should fail because the new root is invalid.
 	pullCmd = exec.Command(dockerBinary, "pull", repoName)
 	pullCmd = exec.Command(dockerBinary, "pull", repoName)
 	s.trustedCmd(pullCmd)
 	s.trustedCmd(pullCmd)
 	out, _, err = runCommandWithOutput(pullCmd)
 	out, _, err = runCommandWithOutput(pullCmd)
-	if err != nil {
-		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
+	if err == nil {
+		c.Fatalf("Continuing with cached data even though it's an invalid root rotation: %s\n%s", err, out)
 	}
 	}
-	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
+	if !strings.Contains(out, "could not rotate trust to a new trusted root") {
 		c.Fatalf("Missing expected output on trusted pull:\n%s", out)
 		c.Fatalf("Missing expected output on trusted pull:\n%s", out)
 	}
 	}
 }
 }

+ 5 - 5
integration-cli/docker_cli_run_test.go

@@ -3432,16 +3432,16 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
 	}
 	}
 
 
-	// Now, try running with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
+	// Now, try running with the original client from this new trust server. This should fail because the new root is invalid.
 	runCmd = exec.Command(dockerBinary, "run", repoName)
 	runCmd = exec.Command(dockerBinary, "run", repoName)
 	s.trustedCmd(runCmd)
 	s.trustedCmd(runCmd)
 	out, _, err = runCommandWithOutput(runCmd)
 	out, _, err = runCommandWithOutput(runCmd)
 
 
-	if err != nil {
-		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
+	if err == nil {
+		c.Fatalf("Continuing with cached data even though it's an invalid root rotation: %s\n%s", err, out)
 	}
 	}
-	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
-		c.Fatalf("Missing expected output on trusted push:\n%s", out)
+	if !strings.Contains(out, "could not rotate trust to a new trusted root") {
+		c.Fatalf("Missing expected output on trusted run:\n%s", out)
 	}
 	}
 }
 }
 
 

+ 19 - 22
integration-cli/fixtures/notary/delgkey1.crt

@@ -1,24 +1,21 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIID8jCCAtqgAwIBAgIJAJkxr+7rAgXbMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
-BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
-A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
-MFoYDzIxMTYwMjEzMDI0MTIwWjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
-FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
-AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJuz
-To1qoL/RY5pNxdPkP/jiO3f/RTvz20C90EweaKgRdIV/vTUUE+mMRQulpf1vpCP9
-uidGfEoJcq4jM1H59XTYUoUvGbAMP3Iu7Uz0rF5v+Glm82Z0WGI+PkOnwRN2bJi4
-LhAch6QlA/48IOFH/O9jnHYMb45lQFpm+gOvatRyGkPZCftD3ntkhVMk1OJ7EZC4
-LYiwzmuPEYusO/qVgcHkGtIxLWAjGmDzrV3Q5orPVwwUOxNQdRRU1L2bhfUsodcb
-Fgi/LCz4xnGx4YpF0O24Y7/0SPotSyaT0RYyj/j/bIKvYB20g4P7469klde1Ariz
-UEIf12PlaJ/H/PaIlEcCAwEAAaOBvDCBuTAdBgNVHQ4EFgQUXZK4ZGswIq54W4VZ
-OJY7zXvvndwwgYkGA1UdIwSBgTB/gBRdkrhkazAirnhbhVk4ljvNe++d3KFcpFow
-WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
-c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCZMa/u
-6wIF2zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA2ktnjxB2lXF+g
-lTc2qp9LhjofgCWKwLgWEeCwXI2nUBNE4n00hA9or2wer2viWC4IJnG0kTyMzzYT
-m1lBpZ8BP6S3sSkvhohaqS+gBIUVB7U65tAof/SY2UHpeVJ1YpTE4F1GAUfqSY7V
-6IGHZAGiLeUS5kC6pzZA4siBhyCoYKRKEb9R82jSCHeFYS3ntwY1/gqcO/uIidVE
-2hLHlx6vBx9BEfXv31AGLoB3YocSTZLATwlrDHUQG1+oNh5ejQU1x/z+Y62EG5Jb
-u0yLDdJeSgup/DzPEoNpSihtdQZytKMK+KBmh22gDA5h+a6620zTZwCvJYxH9kkM
-IClUWwuD
+MIIDhTCCAm2gAwIBAgIJAP2EcMN2UXPcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
+VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ4
+WhcNMjYwNjI4MTc0ODQ4WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
+BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
+ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvgewhaYs
+Ke5s2AM7xxKrT4A6n7hW17qSnBjonCcPcwTFmYqIOdxWjYITgJuHrTwB4ZhBqWS7
+tTsUUu6hWLMeB7Uo5/GEQAAZspKkT9G/rNKF9lbWK9PPhGGkeR01c/Q932m92Hsn
+fCQ0Pp/OzD3nVTh0v9HKk+PObNMOCcqG87eYs4ylPRxs0RrE/rP+bEGssKQSbeCZ
+wazDnO+kiatVgKQZ2CK23iFdRE1z2rzqVDeaFWdvBqrRdWnkOZClhlLgEQ5nK2yV
+B6tSqOiI3MmHyHzIkGOQJp2/s7Pe0ckEkzsjTsJW8oKHlBBl6pRxHIKzNN4VFbeB
+vvYvrogrDrC/owIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUFoHfukRa6qGk1ncON64Z
+ASKlZdkwDQYJKoZIhvcNAQELBQADggEBAEq9Adpd03CPmpbRtTAJGAkjjLFr60sV
+2r+/l/m9R31ZCN9ymM9nxToQ8zfMdeAh/nnPcErziil2gDVqXueCNDkRj09tmDIE
+Q1Oc92uyNZNgcECow77cKZCTZSTku+qsJrYaykH5vSnia8ltcKj8inJedIcpBR+p
+608HEQvF0Eg5eaLPJwH48BCb0Gqdri1dJgrNnqptz7MDr8M+u7tHVulbAd3YxLlq
+JH1W2bkVUx6esbn/MUE5HL5iTuOYREEINvBSmLdmmFkampmCnCB/bDEyJeL9bAkt
+ZPIi0UNSnqFKLSP1Vf8AGLXt6iO7+1OGvtsDXEEYdXVOMsSXZtUuT7A=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 25 - 25
integration-cli/fixtures/notary/delgkey1.key

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
 -----END RSA PRIVATE KEY-----

+ 19 - 22
integration-cli/fixtures/notary/delgkey2.crt

@@ -1,24 +1,21 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIID8jCCAtqgAwIBAgIJAMi/AxlwFquJMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
-BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
-A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
-MloYDzIxMTYwMjEzMDI0MTIyWjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
-FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
-AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/a
-1GO+02jt1p0sME+YGaK4+uZ9jezrpkCXKMsMfItgqCKRTX7YVuR7tnRt/Y1DNVqR
-nMeGc77soDag6eW4xrYrv9LwylUsOLanvK1d/8hDxZhzJjqlJBmz6BvLWDZUF9uu
-OjULL8yuP2cmRogjn0bqmdeKztrZtDQqQiwsG02nVjfuvVi3rP4G4DhL5fUoHB0R
-E6L9Su3/2OWGpdxZqkT7GAbjgLl4/4CXs00493m8xZIHXQ9559PiVlLfk6p6FjEV
-7irZp7XXSe1My/0HGebFXkYqEL9+My2od4w+qJmBT23aTduGTo8IZC7g9lwKEykA
-hWrYhR5tjkLvOsQIE7ECAwEAAaOBvDCBuTAdBgNVHQ4EFgQUHtEAVcwI3k7W5B6c
-L3w+eKQRsIYwgYkGA1UdIwSBgTB/gBQe0QBVzAjeTtbkHpwvfD54pBGwhqFcpFow
-WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
-c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQDIvwMZ
-cBariTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAfjsMtZ+nJ7y5t
-rH9xPwWMLmtC5MwzDRvTUAGbNbFwwm8AncfvsDmmERqsr8L2qhY8CZ9vsN4NjjBn
-QRmM/ynYA8JTbf/5ZNDnD4D6qTXLgGFqyHcBaorcB9uQ8eiMOFAbhxLYfPrKaYdV
-qj+MejcFa3HmzmYCSqsvxRhSje5b4sORe9/3jNheXsX8VZUpWtCHc3k4GiCU6KyS
-gpnXkShU4sG92cK72L8pxmGTz8ynNMj/9WKkLxpNIv5u0/D01a3z4wx5k1zfRZiz
-IQS+xqxV/ztY844MDknxENlYzcqGj0Fd6hE5OKZxnGaH83A5adldMLlnhG1rscGP
-as9uwPYP
+MIIDhTCCAm2gAwIBAgIJAIq8naKlYAQfMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
+VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ4
+WhcNMjYwNjI4MTc0ODQ4WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
+BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
+ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyY2EWYTW
+5VHipw08t675upmD6a+akiuZ1z+XpuOxZCgjZ0aHfoOe8wGKg3Ohz7UCBdD5Mob/
+L/qvRlsCaqPHGZKIyyX1HDO4mpuQQFBhYxt+ZAO3AaawEUOw2rwwMDEjLnDDTSZM
+z8jxCMvsJjBDqgb8g3z+AmjducQ/OH6llldgHIBY8ioRbROCL2PGgqywWq2fThav
+c70YMxtKviBGDNCouYeQ8JMK/PuLwPNDXNQAagFHVARXiUv/ILHk7ImYnSGJUcuk
+JTUGN2MBnpY0eakg7i+4za8sjjqOdn+2I6aVzlGJDSiRP72nkg/cE4BqMl9FrMwK
+9iS8xa9yMDLUvwIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUvQzzFmh3Sv3HcdExY3wx
+/1u6JLAwDQYJKoZIhvcNAQELBQADggEBAJcmDme2Xj/HPUPwaN/EyCmjhY73EiHO
+x6Pm16tscg5JGn5A+u3CZ1DmxUYl8Hp6MaW/sWzdtL0oKJg76pynadCWh5EacFR8
+u+2GV/IcN9mSX6JQzvrqbjSqo5/FehqBD+W5h3euwwApWA3STAadYeyEfmdOA3SQ
+W1vzrA1y7i8qgTqeJ7UX1sEAXlIhBK2zPYaMB+en+ZOiPyNxJYj6IDdGdD2paC9L
+6H9wKC+GAUTSdCWp89HP7ETSXEGr94AXkrwU+qNsiN+OyK8ke0EMngEPh5IQoplw
+/7zEZCth3oKxvR1/4S5LmTVaHI2ZlbU4q9bnY72G4tw8YQr2gcBGo4w=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 25 - 25
integration-cli/fixtures/notary/delgkey2.key

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
 -----END RSA PRIVATE KEY-----

+ 19 - 22
integration-cli/fixtures/notary/delgkey3.crt

@@ -1,24 +1,21 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIID8jCCAtqgAwIBAgIJAI3uONxeFQJtMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
-BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
-A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
-NFoYDzIxMTYwMjEzMDI0MTI0WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
-FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
-AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOii
-Ij01MkSExgurs5owUNxNgRvrZFxNSNGfnscYZiaT/wNcocrOz40vvY29SOBEbCSW
-oBlCi0rYu/7LZBqvsP3YItmifpJHGfRiZ6xEQ4rKznY8+8E3FHVChlmVv9x6QPhA
-9OpATlSLvcdiXHbohdc+kQsl9qM93+QadRQLmtZ6H5Sv90d1MHNViX+8d/k2WyT0
-8u6fNv0ZHeltnZFYruF82YKJCOPdAJnCLUOXWRSG6xDhhvSewjxz6gFla5n8m+D9
-jvmIUUjoMEhjORUIVeA/lXT0AT3Lx0xE8uyhJQbp+hGtcPCcwYFZdz3yLcrxKO47
-nh6qOygf7I2fiR1ogqECAwEAAaOBvDCBuTAdBgNVHQ4EFgQUUqsFJdVoos2aewDh
-m1r66zyXeI4wgYkGA1UdIwSBgTB/gBRSqwUl1WiizZp7AOGbWvrrPJd4jqFcpFow
-WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
-c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCN7jjc
-XhUCbTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQADcyno8/AwNatf
-pjgILCZl1DVrqaKEVbp6ciNgVRCF4cM0bE5W4mjd5tO8d3/yTilry2FPicqxiMps
-oGroMFR+X1cZbOf0U32FyEW4EyWm2jjbiuEpnM5J/EeB/QfckqP6whS/QAM7PxDV
-Sxd8sKDb9SOGZiickFU4QpG1fdmY/knrrtbzRl7Nk/3tBgRaq+Brg7YNZZKlpUNB
-Hp3q0E+MFgVAojpcL7w1oSgoNev+cUNaBdPEmWIEi7F5rosCzmAIhuIY+ghmo9Qg
-zy+byAcxLpujl8vZvE1nZKMKZ7oJayOOgjB2Ztk6bO1r+GPtK5VfqEPhKTRDbBlo
-xS3tSCDJ
+MIIDhTCCAm2gAwIBAgIJAKHt/jxiWqMtMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
+VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ5
+WhcNMjYwNjI4MTc0ODQ5WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
+BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
+ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqfbJk2Dk
+C9FJVjV2+Q2CQrJphG3vFc1Qlu9jgVA5RhGmF9jJzetsclsV/95nBhinIGcSmPQA
+l318G7Bz/cG/6O2n5+hj+S1+YOvQweReZj3d4kCeS86SOyLNTpMD9gsF0S8nR1RN
+h0jD4t1vxAVeGD1o61U8/k0O5eDoeOfOSWZagKk5PhyrMZgNip4IrG46umCkFlrw
+zMMcgQdwTQXywPqkr/LmYpqT1WpMlzHYTQEY8rKorIJQbPtHVYdr4UxYnNmk6fbU
+biEP1DQlwjBWcFTsDLqXKP/K+e3O0/e/hMB0y7Tj9fZ7Viw0t5IKXZPsxMhwknUT
+9vmPzIJO6NiniwIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUdTXRP1EzxQ+UDZSoheVo
+Mobud1cwDQYJKoZIhvcNAQELBQADggEBADV9asTWWdbmpkeRuKyi0xGho39ONK88
+xxkFlco766BVgemo/rGQj3oPuw6M6SzHFoJ6JUPjmLiAQDIGEU/2/b6LcOuLjP+4
+YejCcDTY3lSW/HMNoAmzr2foo/LngNGfe/qhVFUqV7GjFT9+XzFFBfIZ1cQiL2ed
+kc8rgQxFPwWXFCSwaENWeFnMDugkd+7xanoAHq8GsJpg5fTruDTmJkUqC2RNiMLn
+WM7QaqW7+lmUnMnc1IBoz0hFhgoiadWM/1RQxx51zTVw6Au1koIm4ZXu5a+/WyC8
+K1+HyUbc0AVaDaRBpRSOR9aHRwLGh6WQ4aUZQNyJroc999qfYrDEEV8=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 25 - 25
integration-cli/fixtures/notary/delgkey3.key

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
 -----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEA6KIiPTUyRITGC6uzmjBQ3E2BG+tkXE1I0Z+exxhmJpP/A1yh
-ys7PjS+9jb1I4ERsJJagGUKLSti7/stkGq+w/dgi2aJ+kkcZ9GJnrERDisrOdjz7
-wTcUdUKGWZW/3HpA+ED06kBOVIu9x2JcduiF1z6RCyX2oz3f5Bp1FAua1noflK/3
-R3Uwc1WJf7x3+TZbJPTy7p82/Rkd6W2dkViu4XzZgokI490AmcItQ5dZFIbrEOGG
-9J7CPHPqAWVrmfyb4P2O+YhRSOgwSGM5FQhV4D+VdPQBPcvHTETy7KElBun6Ea1w
-8JzBgVl3PfItyvEo7jueHqo7KB/sjZ+JHWiCoQIDAQABAoIBADvh8HpdBTGKFAjR
-DAx2v3nWIZP0RgNUiZgcRJzvdOwdUJmm8KbqEZdAYMTpoqbINiY8971I2d5MaCgw
-ZvZPn3nYdzAamgZBczbrVdCMSe6iQf9Bt3SHHycIFtlcqOSyO6Mr5V+fagptZk66
-zR52wG0l1+RMw25F8SogfV7JlfP7Qh5Bob0lEN2xpbhwLiNaaB+IHNe0FelmRvmJ
-VUonoD0xaos25EXUES7J/9coiBqgRlDVHdUM0oaa/94UnxNPJnoNfte0yd+mC4LZ
-JVHo0Zti3x/8SiCYMbLQs5L8AL8VtPu9OPfur/J8+9Rv0Rh+L1Ben+JWzCzUw1Cj
-abH1zvkCgYEA9Q06Lu69ZLD31fTv46CphN+dGS/VgvMELkob6VQOhbV3RPhe6vqL
-p7D67J53iq4rZY5KX3zuXZ+A5s48atc8gz+hTsrE022QVXmO2ZrE22bEpL+mwpsB
-8//ul1UG51XTw6YR9CmLLD3Y4BgMjhSllx4Wwr9e9+PKl+DuSreqhxMCgYEA8wbf
-P3zh85jPN92gBG8+VIbVlXYOTK0OllYUoLt4URmLRllmrB6LyRlpODCyb9+JymMg
-WvAq5Bc0h8gMbSQEkYaAUq2CfSbyExASUHA+/nZglsTZhPkg5PJImntK6S58KAM7
-RJzyz20gxYA5H4KXFSiF+ONOE9X/cFUPxzF1AfsCgYBfgUY54GYEBkyxIIMWDhnD
-ZXtOw6vNG3V3rP5v04jNZ8oSIVKs9fTT6FADREeGzxauv+QQjxo/dtjAG4TEhxpY
-dMYjdTd8x2jHR1b7TCyI7eaZ5u/RTKRYOlj8tfC43GRqDiFVLZPGLFyIChdqkHVx
-DhME15zls+vTgaCdkjNt7QKBgQCfwDywNx8wSZqtVnoBcD7AwYFUpi3wKTIVkLAu
-mA0XAnuS2uGq8slgf9uynBAvifnBmDeEj6siFD7roozIkYyPPKLNtlC4hAlMjpv7
-VE2UZ6xGb0+tITaGSN2A7trnPS9P/g/PonvZ7hpEuWzTUbyOo/ytBn4ke99VsBSX
-E+OeUQKBgQCgmcwCj2/IH+GOpe9qAG6MTMKK7k22O8fBCrcDybL1pMWIesJEbzpv
-T5Atcx9L5ff6Q4Ysghb8ebXsErv4oZ72xyAwWJmbIaPllWn2ffUikzL3grSriWZy
-0bz6P9sRqYpbdmX3oVvTfBP5kbv+mtDXOB3h5rGfczKWNMyuZmxDOg==
+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
 -----END RSA PRIVATE KEY-----
 -----END RSA PRIVATE KEY-----

+ 19 - 22
integration-cli/fixtures/notary/delgkey4.crt

@@ -1,24 +1,21 @@
 -----BEGIN CERTIFICATE-----
 -----BEGIN CERTIFICATE-----
-MIID8jCCAtqgAwIBAgIJAKKDRMrryBRKMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
-BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
-A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
-N1oYDzIxMTYwMjEzMDI0MTI3WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
-FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
-AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOCf
-Wfff5mX/ko/Y790O04eR7h8/4YtZU3LFItcjhkphMf2V2BRlhWwwW6v96gTN1xsZ
-1il6/YXjviWiLjhrtOVLQBE2yK0A7Wwdh9KJg3QgNqwtFrR1MA1LgWto1F7NyEMC
-9H6Hc95+bgWx1jN0IflfPh1C1m/sA5xGqHDl+8YzJJUOoa5bh04Yk3aIeecatso/
-z7P5c6KicPcZIjhgjxHYB95It/oj8ZuY0hQZb7B5HEGNyBbT2F0vuElWtp+mXexr
-6mzgzvHgaKG36bNCTLxr8BxGA/sbVn01LyI3wpk2uqWzyUFk21M4g2X46OPgKrh7
-2h5b+C0X8DUPi45djHcCAwEAAaOBvDCBuTAdBgNVHQ4EFgQUKcrfRFg+6o2l4xbt
-Ll6hV9pjJh8wgYkGA1UdIwSBgTB/gBQpyt9EWD7qjaXjFu0uXqFX2mMmH6FcpFow
-WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
-c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCig0TK
-68gUSjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAhdKgYUQ36JSPS
-f3Dws09pM5hzSsSae4+nG9XckX6dVQ7sLKmjeeeLrXuzjfygir/+h9cHyShgXFH4
-ZbGpdzf6APG1KRag3/njqEWi+kKZZduxZKvI2EHJhj1xBtf8Qru0TgS7bHPlp9bl
-1/61+aIrtj05LQhqzWzehuJFrmSdWP9cnNbvlPdOdgfgkKakAiLGwwGNvMQbqxaO
-FIB4UPuPdQgm5bpimd5/CThKbpK9/0nr9K4po/m519nvEKxZzsDw5tefGp9Xqly3
-4pk9uyAxO/E2cL0cVA/WHTVTsHPbO7lXxBi6/EjiTUi0Nj1X+btO8+jCLkJyNY0m
-qaiL5k9h
+MIIDhTCCAm2gAwIBAgIJANae++ZkUEWMMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMQ8wDQYD
+VQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb24wHhcNMTYwOTI4MTc0ODQ5
+WhcNMjYwNjI4MTc0ODQ5WjBXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTAT
+BgNVBAcTDFNhbkZyYW5jaXNjbzEPMA0GA1UEChMGRG9ja2VyMRMwEQYDVQQDEwpk
+ZWxlZ2F0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqULAjgba
+Y2I10WfqdmYnPfEqEe6iMDbzcgECb2xKafXcI4ltkQj1iO4zBTs0Ft9EzXFc5ZBh
+pTjZrL6vrIa0y/CH2BiIHBJ0wRHx/40HXp4DSj3HZpVOlEMI3npRfBGNIBllUaRN
+PWG7zL7DcKMIepBfPXyjBsxzH3yNiISq0W5hSiy+ImhSo3aipJUHHcp9Z9NgvpNC
+3QvnxsGKRnECmDRDlxkq+FQu9Iqs/HWFYWgyfcsw+YTrWZq3qVnnqUouHO//c9PG
+Ry3sZSDU97MwvkjvWys1e01Xvd3AbHx08YAsxih58i/OBKe81eD9NuZDP2KrjTxI
+5xkXKhj6DV2NnQIDAQABo1QwUjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUDt95hiqbQvi0KcvZGAUu
+VisnztQwDQYJKoZIhvcNAQELBQADggEBAGi7qHai7MWbfeu6SlXhzIP3AIMa8TMi
+lp/+mvPUFPswIVqYJ71MAN8uA7CTH3z50a2vYupGeOEtZqVJeRf+xgOEpwycncxp
+Qz6wc6TWPVIoT5q1Hqxw1RD2MyKL+Y+QBDYwFxFkthpDMlX48I9frcqoJUWFxBF2
+lnRr/cE7BbPE3sMbXV3wGPlH7+eUf+CgzXJo2HB6THzagyEgNrDiz/0rCQa1ipFd
+mNU3D/U6BFGmJNxhvSOtXX9escg8yjr05YwwzokHS2K4jE0ZuJPBd50C/Rvo3Mf4
+0h7/2Q95e7d42zPe9WYPu2F8KTWsf4r+6ddhKrKhYzXIcTAfHIOiO+U=
 -----END CERTIFICATE-----
 -----END CERTIFICATE-----

+ 25 - 25
integration-cli/fixtures/notary/delgkey4.key

@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
 -----END RSA PRIVATE KEY-----

+ 18 - 0
integration-cli/fixtures/notary/gen.sh

@@ -0,0 +1,18 @@
+for selfsigned in delgkey1 delgkey2 delgkey3 delgkey4; do
+        subj='/C=US/ST=CA/L=SanFrancisco/O=Docker/CN=delegation'
+
+        openssl genrsa -out "${selfsigned}.key" 2048
+        openssl req -new -key "${selfsigned}.key" -out "${selfsigned}.csr" -sha256 -subj "${subj}"
+        cat > "${selfsigned}.cnf" <<EOL
+[selfsigned]
+basicConstraints = critical,CA:FALSE
+keyUsage = critical, digitalSignature, keyEncipherment
+extendedKeyUsage=codeSigning
+subjectKeyIdentifier=hash
+EOL
+
+        openssl x509 -req -days 3560 -in "${selfsigned}.csr" -signkey "${selfsigned}.key" -sha256 \
+                -out "${selfsigned}.crt" -extfile "${selfsigned}.cnf" -extensions selfsigned
+
+        rm "${selfsigned}.cnf" "${selfsigned}.csr"
+done

+ 2 - 0
vendor/src/github.com/docker/notary/.gitignore

@@ -1,3 +1,4 @@
+/.vscode
 /cmd/notary-server/notary-server
 /cmd/notary-server/notary-server
 /cmd/notary-server/local.config.json
 /cmd/notary-server/local.config.json
 /cmd/notary-signer/local.config.json
 /cmd/notary-signer/local.config.json
@@ -8,4 +9,5 @@ cross
 *.swp
 *.swp
 .idea
 .idea
 *.iml
 *.iml
+*.test
 coverage.out
 coverage.out

+ 61 - 0
vendor/src/github.com/docker/notary/CHANGELOG.md

@@ -1,5 +1,66 @@
 # Changelog
 # Changelog
 
 
+## [v0.4.2](https://github.com/docker/notary/releases/tag/v0.4.2) 9/30/2016
++ Bump the cross compiler to golang 1.7.1, since [1.6.3 builds binaries that could have non-deterministic bugs in OS X Sierra](https://groups.google.com/forum/#!msg/golang-dev/Jho5sBHZgAg/cq6d97S1AwAJ) [#984](https://github.com/docker/notary/pull/984)
+
+## [v0.4.1](https://github.com/docker/notary/releases/tag/v0.4.1) 9/27/2016
++ Preliminary Windows support for notary client [#970](https://github.com/docker/notary/pull/970)
++ Output message to CLI when repo changes have been successfully published [#974](https://github.com/docker/notary/pull/974)
++ Improved error messages for client authentication errors and for the witness command [#972](https://github.com/docker/notary/pull/972)
++ Support for finding keys that are anywhere in the notary directory's "private" directory, not just under "private/root_keys" or "private/tuf_keys" [#981](https://github.com/docker/notary/pull/981)
++ Previously, on any error updating, the client would fall back on the cache.  Now we only do so if there is a network error or if the server is unavailable or missing the TUF data. Invalid TUF data will cause the update to fail - for example if there was an invalid root rotation. [#982](https://github.com/docker/notary/pull/982)
+
+## [v0.4.0](https://github.com/docker/notary/releases/tag/v0.4.0) 9/21/2016
++ Server-managed key rotations [#889](https://github.com/docker/notary/pull/889)
++ Remove `timestamp_keys` table, which stored redundant information [#889](https://github.com/docker/notary/pull/889)
++ Introduce `notary delete` command to delete local and/or remote repo data [#895](https://github.com/docker/notary/pull/895)
++ Introduce `notary witness` command to stage signatures for specified roles [#875](https://github.com/docker/notary/pull/875)
++ Add `-p` flag to offline commands to attempt auto-publish [#886](https://github.com/docker/notary/pull/886) [#912](https://github.com/docker/notary/pull/912) [#923](https://github.com/docker/notary/pull/923)
++ Introduce `notary reset` command to manage staged changes [#959](https://github.com/docker/notary/pull/959) [#856](https://github.com/docker/notary/pull/856)
++ Add `--rootkey` flag to `notary init` to provide a private root key for a repo [#801](https://github.com/docker/notary/pull/801)
++ Introduce `notary delegation purge` command to remove a specified key from all delegations [#855](https://github.com/docker/notary/pull/855)
++ Removed HTTP endpoint from notary-signer [#870](https://github.com/docker/notary/pull/870)
++ Refactored and unified key storage [#825](https://github.com/docker/notary/pull/825)
++ Batched key import and export now operate on PEM files (potentially with multiple blocks) instead of ZIP [#825](https://github.com/docker/notary/pull/825) [#882](https://github.com/docker/notary/pull/882)
++ Add full database integration test-suite [#824](https://github.com/docker/notary/pull/824) [#854](https://github.com/docker/notary/pull/854) [#863](https://github.com/docker/notary/pull/863)
++ Improve notary-server, trust pinning, and yubikey logging [#798](https://github.com/docker/notary/pull/798) [#858](https://github.com/docker/notary/pull/858) [#891](https://github.com/docker/notary/pull/891)
++ Warn if certificates for root or delegations are near expiry [#802](https://github.com/docker/notary/pull/802)
++ Warn if role metadata is near expiry [#786](https://github.com/docker/notary/pull/786)
++ Reformat CLI table output to use the `text/tabwriter` package [#809](https://github.com/docker/notary/pull/809)
++ Fix passphrase retrieval attempt counting and terminal detection [#906](https://github.com/docker/notary/pull/906)
++ Fix listing nested delegations [#864](https://github.com/docker/notary/pull/864)
++ Bump go version to 1.6.3, fix go1.7 compatibility [#851](https://github.com/docker/notary/pull/851) [#793](https://github.com/docker/notary/pull/793)
++ Convert docker-compose files to v2 format [#755](https://github.com/docker/notary/pull/755)
++ Validate root rotations against trust pinning [#800](https://github.com/docker/notary/pull/800)
++ Update fixture certificates for two-year expiry window [#951](https://github.com/docker/notary/pull/951)
+
+## [v0.3.0](https://github.com/docker/notary/releases/tag/v0.3.0) 5/11/2016
++ Root rotations
++ RethinkDB support as a storage backend for Server and Signer
++ A new TUF repo builder that merges server and client validation
++ Trust Pinning: configure known good key IDs and CAs to replace TOFU.
++ Add --input, --output, and --quiet flags to notary verify command
++ Remove local certificate store. It was redundant as all certs were also stored in the cached root.json
++ Cleanup of dead code in client side key storage logic
++ Update project to Go 1.6.1
++ Reorganize vendoring to meet Go 1.6+ standard. Still using Godeps to manage vendored packages
++ Add targets by hash, no longer necessary to have the original target data available
++ Active Key ID verification during signature verification
++ Switch all testing from assert to require, reduces noise in test runs
++ Use alpine based images for smaller downloads and faster setup times
++ Clean up out of data signatures when re-signing content
++ Set cache control headers on HTTP responses from Notary Server
++ Add sha512 support for targets
++ Add environment variable for delegation key passphrase
++ Reduce permissions requested by client from token server
++ Update formatting for delegation list output
++ Move SQLite dependency to tests only so it doesn't get built into official images
++ Fixed asking for password to list private repositories
++ Enable using notary client with username/password in a scripted fashion
++ Fix static compilation of client
++ Enforce TUF version to be >= 1, previously 0 was acceptable although unused
++ json.RawMessage should always be used as *json.RawMessage due to concepts of addressability in Go and effects on encoding
+
 ## [v0.2](https://github.com/docker/notary/releases/tag/v0.2.0) 2/24/2016
 ## [v0.2](https://github.com/docker/notary/releases/tag/v0.2.0) 2/24/2016
 + Add support for delegation roles in `notary` server and client
 + Add support for delegation roles in `notary` server and client
 + Add `notary CLI` commands for managing delegation roles: `notary delegation`
 + Add `notary CLI` commands for managing delegation roles: `notary delegation`

+ 7 - 4
vendor/src/github.com/docker/notary/Dockerfile

@@ -1,4 +1,4 @@
-FROM golang:1.6.1
+FROM golang:1.7.1
 
 
 RUN apt-get update && apt-get install -y \
 RUN apt-get update && apt-get install -y \
 	curl \
 	curl \
@@ -8,10 +8,14 @@ RUN apt-get update && apt-get install -y \
 	patch \
 	patch \
 	tar \
 	tar \
 	xz-utils \
 	xz-utils \
+	python \
+	python-pip \
 	--no-install-recommends \
 	--no-install-recommends \
 	&& rm -rf /var/lib/apt/lists/*
 	&& rm -rf /var/lib/apt/lists/*
 
 
-RUN go get golang.org/x/tools/cmd/cover
+RUN useradd -ms /bin/bash notary \
+	&& pip install codecov \
+	&& go get golang.org/x/tools/cmd/cover github.com/golang/lint/golint github.com/client9/misspell/cmd/misspell github.com/gordonklaus/ineffassign
 
 
 # Configure the container for OSX cross compilation
 # Configure the container for OSX cross compilation
 ENV OSX_SDK MacOSX10.11.sdk
 ENV OSX_SDK MacOSX10.11.sdk
@@ -27,8 +31,7 @@ ENV PATH /osxcross/target/bin:$PATH
 ENV NOTARYDIR /go/src/github.com/docker/notary
 ENV NOTARYDIR /go/src/github.com/docker/notary
 
 
 COPY . ${NOTARYDIR}
 COPY . ${NOTARYDIR}
-
-ENV GOPATH ${NOTARYDIR}/Godeps/_workspace:$GOPATH
+RUN chmod -R a+rw /go
 
 
 WORKDIR ${NOTARYDIR}
 WORKDIR ${NOTARYDIR}
 
 

+ 34 - 48
vendor/src/github.com/docker/notary/Makefile

@@ -13,13 +13,15 @@ endif
 CTIMEVAR=-X $(NOTARY_PKG)/version.GitCommit=$(GITCOMMIT) -X $(NOTARY_PKG)/version.NotaryVersion=$(NOTARY_VERSION)
 CTIMEVAR=-X $(NOTARY_PKG)/version.GitCommit=$(GITCOMMIT) -X $(NOTARY_PKG)/version.NotaryVersion=$(NOTARY_VERSION)
 GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
 GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
 GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
 GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
-GOOSES = darwin linux
+GOOSES = darwin linux windows
 NOTARY_BUILDTAGS ?= pkcs11
 NOTARY_BUILDTAGS ?= pkcs11
 NOTARYDIR := /go/src/github.com/docker/notary
 NOTARYDIR := /go/src/github.com/docker/notary
 
 
-GO_VERSION := $(shell go version | grep "1\.[6-9]\(\.[0-9]+\)*")
-# check to make sure we have the right version
-ifeq ($(strip $(GO_VERSION)),)
+GO_VERSION := $(shell go version | grep "1\.[6-9]\(\.[0-9]+\)*\|devel")
+# check to make sure we have the right version. development versions of Go are
+# not officially supported, but allowed for building
+
+ifeq ($(strip $(GO_VERSION))$(SKIPENVCHECK),)
 $(error Bad Go version - please install Go >= 1.6)
 $(error Bad Go version - please install Go >= 1.6)
 endif
 endif
 
 
@@ -40,13 +42,11 @@ COVERPROFILE?=$(COVERDIR)/cover.out
 COVERMODE=count
 COVERMODE=count
 PKGS ?= $(shell go list -tags "${NOTARY_BUILDTAGS}" ./... | grep -v /vendor/ | tr '\n' ' ')
 PKGS ?= $(shell go list -tags "${NOTARY_BUILDTAGS}" ./... | grep -v /vendor/ | tr '\n' ' ')
 
 
-GO_VERSION = $(shell go version | awk '{print $$3}')
-
-.PHONY: clean all fmt vet lint build test binaries cross cover docker-images notary-dockerfile
+.PHONY: clean all lint build test binaries cross cover docker-images notary-dockerfile
 .DELETE_ON_ERROR: cover
 .DELETE_ON_ERROR: cover
 .DEFAULT: default
 .DEFAULT: default
 
 
-all: AUTHORS clean fmt vet fmt lint build test binaries
+all: AUTHORS clean lint build test binaries
 
 
 AUTHORS: .git/HEAD
 AUTHORS: .git/HEAD
 	git log --format='%aN <%aE>' | sort -fu > $@
 	git log --format='%aN <%aE>' | sort -fu > $@
@@ -90,32 +90,27 @@ ${PREFIX}/bin/static/notary:
 	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary
 	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary
 endif
 endif
 
 
-vet:
-	@echo "+ $@"
+
+# run all lint functionality - excludes Godep directory, vendoring, binaries, python tests, and git files
+lint:
+	@echo "+ $@: golint, go vet, go fmt, misspell, ineffassign"
+	# golint
+	@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec golint {} \; | tee /dev/stderr)"
+	# gofmt
+	@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
+	# govet
 ifeq ($(shell uname -s), Darwin)
 ifeq ($(shell uname -s), Darwin)
 	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs echo "This file should end with '_test':"  | tee /dev/stderr)"
 	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs echo "This file should end with '_test':"  | tee /dev/stderr)"
 else
 else
 	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs -r echo "This file should end with '_test':"  | tee /dev/stderr)"
 	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs -r echo "This file should end with '_test':"  | tee /dev/stderr)"
 endif
 endif
 	@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
 	@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
-
-fmt:
-	@echo "+ $@"
-	@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
-
-lint:
-	@echo "+ $@"
-	@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec golint {} \; | tee /dev/stderr)"
-
-# Requires that the following:
-# go get -u github.com/client9/misspell/cmd/misspell
-#
-# be run first
-
-# misspell target, don't include Godeps, binaries, python tests, or git files
-misspell:
-	@echo "+ $@"
-	@test -z "$$(find . -name '*' | grep -v vendor/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
+	# misspell - requires that the following be run first:
+	#    go get -u github.com/client9/misspell/cmd/misspell
+	@test -z "$$(find . -type f | grep -v vendor/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | grep -v \.pdf | xargs misspell | tee /dev/stderr)"
+	# ineffassign - requires that the following be run first:
+	#    go get -u github.com/gordonklaus/ineffassign
+	@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec ineffassign {} \; | tee /dev/stderr)"
 
 
 build:
 build:
 	@echo "+ $@"
 	@echo "+ $@"
@@ -130,15 +125,13 @@ test:
 	@echo
 	@echo
 	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) $(PKGS)
 	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) $(PKGS)
 
 
-test-full: TESTOPTS =
-test-full: vet lint
-	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
-	@echo "+ $@"
-	@echo
-	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v $(PKGS)
+integration: TESTDB = mysql
+integration: clean
+	buildscripts/integrationtest.sh $(TESTDB)
 
 
-integration:
-	buildscripts/integrationtest.sh development.yml
+testdb: TESTDB = mysql
+testdb:
+	buildscripts/dbtests.sh $(TESTDB)
 
 
 protos:
 protos:
 	@protoc --go_out=plugins=grpc:. proto/*.proto
 	@protoc --go_out=plugins=grpc:. proto/*.proto
@@ -148,25 +141,19 @@ protos:
 # go get github.com/wadey/gocovmerge; go install github.com/wadey/gocovmerge
 # go get github.com/wadey/gocovmerge; go install github.com/wadey/gocovmerge
 #
 #
 # be run first
 # be run first
-
-define gocover
-go test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
-endef
-
+gen-cover:
 gen-cover:
 gen-cover:
 	@mkdir -p "$(COVERDIR)"
 	@mkdir -p "$(COVERDIR)"
-	$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
-	rm -f "$(COVERDIR)"/*testutils*.coverage.txt
+	python -u buildscripts/covertest.py --coverdir "$(COVERDIR)" --tags "$(NOTARY_BUILDTAGS)" --pkgs="$(PKGS)" --testopts="${TESTOPTS}"
 
 
 # Generates the cover binaries and runs them all in serial, so this can be used
 # Generates the cover binaries and runs them all in serial, so this can be used
 # run all tests with a yubikey without any problems
 # run all tests with a yubikey without any problems
-cover: OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
 cover: gen-cover covmerge
 cover: gen-cover covmerge
 	@go tool cover -html="$(COVERPROFILE)"
 	@go tool cover -html="$(COVERPROFILE)"
 
 
 # Generates the cover binaries and runs them all in serial, so this can be used
 # Generates the cover binaries and runs them all in serial, so this can be used
 # run all tests with a yubikey without any problems
 # run all tests with a yubikey without any problems
-ci: OPTS = -tags "${NOTARY_BUILDTAGS}" -race -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
+ci: override TESTOPTS = -race
 # Codecov knows how to merge multiple coverage files, so covmerge is not needed
 # Codecov knows how to merge multiple coverage files, so covmerge is not needed
 ci: gen-cover
 ci: gen-cover
 
 
@@ -205,10 +192,9 @@ shell: notary-dockerfile
 
 
 cross: notary-dockerfile
 cross: notary-dockerfile
 	@rm -rf $(CURDIR)/cross
 	@rm -rf $(CURDIR)/cross
-	docker run --rm -v $(CURDIR)/cross:$(NOTARYDIR)/cross -e NOTARY_BUILDTAGS=$(NOTARY_BUILDTAGS) notary buildscripts/cross.sh $(GOOSES)
-
+	docker run --rm -v $(CURDIR)/cross:$(NOTARYDIR)/cross -e CTIMEVAR="${CTIMEVAR}" -e NOTARY_BUILDTAGS=$(NOTARY_BUILDTAGS) notary buildscripts/cross.sh $(GOOSES)
 
 
 clean:
 clean:
 	@echo "+ $@"
 	@echo "+ $@"
-	@rm -rf "$(COVERDIR)"
+	@rm -rf "$(COVERDIR)" cross
 	@rm -rf "${PREFIX}/bin/notary-server" "${PREFIX}/bin/notary" "${PREFIX}/bin/notary-signer"
 	@rm -rf "${PREFIX}/bin/notary-server" "${PREFIX}/bin/notary" "${PREFIX}/bin/notary-signer"

+ 1 - 1
vendor/src/github.com/docker/notary/NOTARY_VERSION

@@ -1 +1 @@
-0.2
+0.4.2

+ 3 - 2
vendor/src/github.com/docker/notary/README.md

@@ -1,5 +1,5 @@
 # Notary
 # Notary
-[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary)
+[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary) [![GoReportCard](https://goreportcard.com/badge/docker/notary)](https://goreportcard.com/report/github.com/docker/notary)
 
 
 The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
 The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
 with trusted collections.  Please see the [service architecture](docs/service_architecture.md) documentation
 with trusted collections.  Please see the [service architecture](docs/service_architecture.md) documentation
@@ -80,7 +80,8 @@ to use `notary` with Docker images.
 
 
 Prerequisites:
 Prerequisites:
 
 
-- Go >= 1.6.1
+- Go >= 1.7
+
 - [godep](https://github.com/tools/godep) installed
 - [godep](https://github.com/tools/godep) installed
 - libtool development headers installed
 - libtool development headers installed
     - Ubuntu: `apt-get install libltdl-dev`
     - Ubuntu: `apt-get install libltdl-dev`

+ 6 - 70
vendor/src/github.com/docker/notary/circle.yml

@@ -1,87 +1,23 @@
-# Pony-up!
 machine:
 machine:
   pre:
   pre:
-  # Install gvm
-    - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
   # Upgrade docker
   # Upgrade docker
-    - sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci'
-    - sudo chmod 0755 /usr/bin/docker
-
-  post:
-  # Install many go versions
-    - gvm install go1.6.1 -B --name=stable
+    - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
   # upgrade compose
   # upgrade compose
     - sudo pip install --upgrade docker-compose
     - sudo pip install --upgrade docker-compose
 
 
   services:
   services:
     - docker
     - docker
 
 
-  environment:
-  # Convenient shortcuts to "common" locations
-    CHECKOUT: /home/ubuntu/$CIRCLE_PROJECT_REPONAME
-    BASE_DIR: src/github.com/docker/notary
-  # Trick circle brainflat "no absolute path" behavior
-    BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
-  # Workaround Circle parsing dumb bugs and/or YAML wonkyness
-    CIRCLE_PAIN: "mode: set"
-  # Put the coverage profile somewhere codecov's script can find it
-    COVERPROFILE: coverage.out
-
-  hosts:
-  # Not used yet
-    fancy: 127.0.0.1
-
 dependencies:
 dependencies:
-  pre:
-  # Copy the code to the gopath of all go versions
-    - >
-      gvm use stable &&
-      mkdir -p "$(dirname $BASE_STABLE)" &&
-      cp -R "$CHECKOUT" "$BASE_STABLE"
-
   override:
   override:
-   # don't use circleci's default dependency installation step of `go get -d -u ./...`
-   # since we already vendor everything; additionally install linting and misspell tools
-    - >
-      gvm use stable &&
-      go get github.com/golang/lint/golint &&
-      go get -u github.com/client9/misspell/cmd/misspell
+    - docker build -t notary_client .
 
 
 test:
 test:
-  pre:
-  # Output the go versions we are going to test
-    - gvm use stable && go version
-
-  # CLEAN
-    - gvm use stable && make clean:
-        pwd: $BASE_STABLE
-
-  # FMT
-    - gvm use stable && make fmt:
-        pwd: $BASE_STABLE
-
-  # VET
-    - gvm use stable && make vet:
-        pwd: $BASE_STABLE
-
-  # LINT
-    - gvm use stable && make lint:
-        pwd: $BASE_STABLE
-
-  # MISSPELL
-    - gvm use stable && make misspell:
-        pwd: $BASE_STABLE
-
   override:
   override:
-  # Test stable, and report
-  # hacking this to be parallel
-    - case $CIRCLE_NODE_INDEX in 0) gvm use stable && NOTARY_BUILDTAGS=pkcs11 make ci ;; 1) gvm use stable && NOTARY_BUILDTAGS=none make ci ;; 2) gvm use stable && make integration ;; esac:
+  # circleci only supports manual parellism
+    - buildscripts/circle_parallelism.sh:
         parallel: true
         parallel: true
         timeout: 600
         timeout: 600
-        pwd: $BASE_STABLE
-
   post:
   post:
-  # Report to codecov.io
-    - case $CIRCLE_NODE_INDEX in 0) bash <(curl -s https://codecov.io/bash) ;; 1) bash <(curl -s https://codecov.io/bash) ;; esac:
-        parallel: true
-        pwd: $BASE_STABLE
+    - docker-compose -f docker-compose.yml down -v
+    - docker-compose -f docker-compose.rethink.yml down -v

+ 19 - 18
vendor/src/github.com/docker/notary/client/changelist/change.go

@@ -4,7 +4,7 @@ import (
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 )
 )
 
 
-// Scopes for TufChanges are simply the TUF roles.
+// Scopes for TUFChanges are simply the TUF roles.
 // Unfortunately because of targets delegations, we can only
 // Unfortunately because of targets delegations, we can only
 // cover the base roles.
 // cover the base roles.
 const (
 const (
@@ -14,7 +14,7 @@ const (
 	ScopeTimestamp = "timestamp"
 	ScopeTimestamp = "timestamp"
 )
 )
 
 
-// Types for TufChanges are namespaced by the Role they
+// Types for TUFChanges are namespaced by the Role they
 // are relevant for. The Root and Targets roles are the
 // are relevant for. The Root and Targets roles are the
 // only ones for which user action can cause a change, as
 // only ones for which user action can cause a change, as
 // all changes in Snapshot and Timestamp are programmatically
 // all changes in Snapshot and Timestamp are programmatically
@@ -23,10 +23,11 @@ const (
 	TypeRootRole          = "role"
 	TypeRootRole          = "role"
 	TypeTargetsTarget     = "target"
 	TypeTargetsTarget     = "target"
 	TypeTargetsDelegation = "delegation"
 	TypeTargetsDelegation = "delegation"
+	TypeWitness           = "witness"
 )
 )
 
 
-// TufChange represents a change to a TUF repo
-type TufChange struct {
+// TUFChange represents a change to a TUF repo
+type TUFChange struct {
 	// Abbreviated because Go doesn't permit a field and method of the same name
 	// Abbreviated because Go doesn't permit a field and method of the same name
 	Actn       string `json:"action"`
 	Actn       string `json:"action"`
 	Role       string `json:"role"`
 	Role       string `json:"role"`
@@ -35,16 +36,16 @@ type TufChange struct {
 	Data       []byte `json:"data"`
 	Data       []byte `json:"data"`
 }
 }
 
 
-// TufRootData represents a modification of the keys associated
+// TUFRootData represents a modification of the keys associated
 // with a role that appears in the root.json
 // with a role that appears in the root.json
-type TufRootData struct {
+type TUFRootData struct {
 	Keys     data.KeyList `json:"keys"`
 	Keys     data.KeyList `json:"keys"`
 	RoleName string       `json:"role"`
 	RoleName string       `json:"role"`
 }
 }
 
 
-// NewTufChange initializes a tufChange object
-func NewTufChange(action string, role, changeType, changePath string, content []byte) *TufChange {
-	return &TufChange{
+// NewTUFChange initializes a TUFChange object
+func NewTUFChange(action string, role, changeType, changePath string, content []byte) *TUFChange {
+	return &TUFChange{
 		Actn:       action,
 		Actn:       action,
 		Role:       role,
 		Role:       role,
 		ChangeType: changeType,
 		ChangeType: changeType,
@@ -54,34 +55,34 @@ func NewTufChange(action string, role, changeType, changePath string, content []
 }
 }
 
 
 // Action return c.Actn
 // Action return c.Actn
-func (c TufChange) Action() string {
+func (c TUFChange) Action() string {
 	return c.Actn
 	return c.Actn
 }
 }
 
 
 // Scope returns c.Role
 // Scope returns c.Role
-func (c TufChange) Scope() string {
+func (c TUFChange) Scope() string {
 	return c.Role
 	return c.Role
 }
 }
 
 
 // Type returns c.ChangeType
 // Type returns c.ChangeType
-func (c TufChange) Type() string {
+func (c TUFChange) Type() string {
 	return c.ChangeType
 	return c.ChangeType
 }
 }
 
 
 // Path return c.ChangePath
 // Path return c.ChangePath
-func (c TufChange) Path() string {
+func (c TUFChange) Path() string {
 	return c.ChangePath
 	return c.ChangePath
 }
 }
 
 
 // Content returns c.Data
 // Content returns c.Data
-func (c TufChange) Content() []byte {
+func (c TUFChange) Content() []byte {
 	return c.Data
 	return c.Data
 }
 }
 
 
-// TufDelegation represents a modification to a target delegation
+// TUFDelegation represents a modification to a target delegation
 // this includes creating a delegations. This format is used to avoid
 // this includes creating a delegations. This format is used to avoid
 // unexpected race conditions between humans modifying the same delegation
 // unexpected race conditions between humans modifying the same delegation
-type TufDelegation struct {
+type TUFDelegation struct {
 	NewName       string       `json:"new_name,omitempty"`
 	NewName       string       `json:"new_name,omitempty"`
 	NewThreshold  int          `json:"threshold, omitempty"`
 	NewThreshold  int          `json:"threshold, omitempty"`
 	AddKeys       data.KeyList `json:"add_keys, omitempty"`
 	AddKeys       data.KeyList `json:"add_keys, omitempty"`
@@ -91,8 +92,8 @@ type TufDelegation struct {
 	ClearAllPaths bool         `json:"clear_paths,omitempty"`
 	ClearAllPaths bool         `json:"clear_paths,omitempty"`
 }
 }
 
 
-// ToNewRole creates a fresh role object from the TufDelegation data
-func (td TufDelegation) ToNewRole(scope string) (*data.Role, error) {
+// ToNewRole creates a fresh role object from the TUFDelegation data
+func (td TUFDelegation) ToNewRole(scope string) (*data.Role, error) {
 	name := scope
 	name := scope
 	if td.NewName != "" {
 	if td.NewName != "" {
 		name = td.NewName
 		name = td.NewName

+ 18 - 0
vendor/src/github.com/docker/notary/client/changelist/changelist.go

@@ -21,6 +21,24 @@ func (cl *memChangelist) Add(c Change) error {
 	return nil
 	return nil
 }
 }
 
 
+// Remove deletes the changes found at the given indices
+func (cl *memChangelist) Remove(idxs []int) error {
+	remove := make(map[int]struct{})
+	for _, i := range idxs {
+		remove[i] = struct{}{}
+	}
+	var keep []Change
+
+	for i, c := range cl.changes {
+		if _, ok := remove[i]; ok {
+			continue
+		}
+		keep = append(keep, c)
+	}
+	cl.changes = keep
+	return nil
+}
+
 // Clear empties the changelist file.
 // Clear empties the changelist file.
 func (cl *memChangelist) Clear(archive string) error {
 func (cl *memChangelist) Clear(archive string) error {
 	// appending to a nil list initializes it.
 	// appending to a nil list initializes it.

+ 30 - 9
vendor/src/github.com/docker/notary/client/changelist/file_changelist.go

@@ -5,12 +5,12 @@ import (
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
-	"path"
 	"sort"
 	"sort"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/uuid"
 	"github.com/docker/distribution/uuid"
+	"path/filepath"
 )
 )
 
 
 // FileChangelist stores all the changes as files
 // FileChangelist stores all the changes as files
@@ -46,13 +46,14 @@ func getFileNames(dirName string) ([]os.FileInfo, error) {
 		}
 		}
 		fileInfos = append(fileInfos, f)
 		fileInfos = append(fileInfos, f)
 	}
 	}
+	sort.Sort(fileChanges(fileInfos))
 	return fileInfos, nil
 	return fileInfos, nil
 }
 }
 
 
-// Read a JSON formatted file from disk; convert to TufChange struct
-func unmarshalFile(dirname string, f os.FileInfo) (*TufChange, error) {
-	c := &TufChange{}
-	raw, err := ioutil.ReadFile(path.Join(dirname, f.Name()))
+// Read a JSON formatted file from disk; convert to TUFChange struct
+func unmarshalFile(dirname string, f os.FileInfo) (*TUFChange, error) {
+	c := &TUFChange{}
+	raw, err := ioutil.ReadFile(filepath.Join(dirname, f.Name()))
 	if err != nil {
 	if err != nil {
 		return c, err
 		return c, err
 	}
 	}
@@ -70,7 +71,6 @@ func (cl FileChangelist) List() []Change {
 	if err != nil {
 	if err != nil {
 		return changes
 		return changes
 	}
 	}
-	sort.Sort(fileChanges(fileInfos))
 	for _, f := range fileInfos {
 	for _, f := range fileInfos {
 		c, err := unmarshalFile(cl.dir, f)
 		c, err := unmarshalFile(cl.dir, f)
 		if err != nil {
 		if err != nil {
@@ -89,10 +89,32 @@ func (cl FileChangelist) Add(c Change) error {
 		return err
 		return err
 	}
 	}
 	filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
 	filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
-	return ioutil.WriteFile(path.Join(cl.dir, filename), cJSON, 0644)
+	return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0644)
+}
+
+// Remove deletes the changes found at the given indices
+func (cl FileChangelist) Remove(idxs []int) error {
+	fileInfos, err := getFileNames(cl.dir)
+	if err != nil {
+		return err
+	}
+	remove := make(map[int]struct{})
+	for _, i := range idxs {
+		remove[i] = struct{}{}
+	}
+	for i, c := range fileInfos {
+		if _, ok := remove[i]; ok {
+			file := filepath.Join(cl.dir, c.Name())
+			if err := os.Remove(file); err != nil {
+				logrus.Errorf("could not remove change %d: %s", i, err.Error())
+			}
+		}
+	}
+	return nil
 }
 }
 
 
 // Clear clears the change list
 // Clear clears the change list
+// N.B. archiving not currently implemented
 func (cl FileChangelist) Clear(archive string) error {
 func (cl FileChangelist) Clear(archive string) error {
 	dir, err := os.Open(cl.dir)
 	dir, err := os.Open(cl.dir)
 	if err != nil {
 	if err != nil {
@@ -104,7 +126,7 @@ func (cl FileChangelist) Clear(archive string) error {
 		return err
 		return err
 	}
 	}
 	for _, f := range files {
 	for _, f := range files {
-		os.Remove(path.Join(cl.dir, f.Name()))
+		os.Remove(filepath.Join(cl.dir, f.Name()))
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -121,7 +143,6 @@ func (cl FileChangelist) NewIterator() (ChangeIterator, error) {
 	if err != nil {
 	if err != nil {
 		return &FileChangeListIterator{}, err
 		return &FileChangeListIterator{}, err
 	}
 	}
-	sort.Sort(fileChanges(fileInfos))
 	return &FileChangeListIterator{dirname: cl.dir, collection: fileInfos}, nil
 	return &FileChangeListIterator{dirname: cl.dir, collection: fileInfos}, nil
 }
 }
 
 

+ 3 - 0
vendor/src/github.com/docker/notary/client/changelist/interface.go

@@ -15,6 +15,9 @@ type Changelist interface {
 	// to save a copy of the changelist in that location
 	// to save a copy of the changelist in that location
 	Clear(archive string) error
 	Clear(archive string) error
 
 
+	// Remove deletes the changes corresponding with the indices given
+	Remove(idxs []int) error
+
 	// Close syncronizes any pending writes to the underlying
 	// Close syncronizes any pending writes to the underlying
 	// storage and closes the file/connection
 	// storage and closes the file/connection
 	Close() error
 	Close() error

+ 131 - 44
vendor/src/github.com/docker/notary/client/client.go

@@ -16,13 +16,12 @@ import (
 	"github.com/docker/notary"
 	"github.com/docker/notary"
 	"github.com/docker/notary/client/changelist"
 	"github.com/docker/notary/client/changelist"
 	"github.com/docker/notary/cryptoservice"
 	"github.com/docker/notary/cryptoservice"
+	store "github.com/docker/notary/storage"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/tuf"
 	"github.com/docker/notary/tuf"
-	tufclient "github.com/docker/notary/tuf/client"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/store"
 	"github.com/docker/notary/tuf/utils"
 	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
@@ -85,6 +84,7 @@ type NotaryRepository struct {
 	fileStore     store.MetadataStore
 	fileStore     store.MetadataStore
 	CryptoService signed.CryptoService
 	CryptoService signed.CryptoService
 	tufRepo       *tuf.Repo
 	tufRepo       *tuf.Repo
+	invalid       *tuf.Repo // known data that was parsable but deemed invalid
 	roundTrip     http.RoundTripper
 	roundTrip     http.RoundTripper
 	trustPinning  trustpinning.TrustPinConfig
 	trustPinning  trustpinning.TrustPinConfig
 }
 }
@@ -121,7 +121,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
 }
 }
 
 
 // Target represents a simplified version of the data TUF operates on, so external
 // Target represents a simplified version of the data TUF operates on, so external
-// applications don't have to depend on tuf data types.
+// applications don't have to depend on TUF data types.
 type Target struct {
 type Target struct {
 	Name   string      // the name of the target
 	Name   string      // the name of the target
 	Hashes data.Hashes // the hash of the target
 	Hashes data.Hashes // the hash of the target
@@ -159,7 +159,7 @@ func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	x509PublicKey := trustmanager.CertToKey(cert)
+	x509PublicKey := utils.CertToKey(cert)
 	if x509PublicKey == nil {
 	if x509PublicKey == nil {
 		return nil, fmt.Errorf(
 		return nil, fmt.Errorf(
 			"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
 			"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
@@ -173,10 +173,14 @@ func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
 // timestamp key and possibly other serverManagedRoles), but the created repository
 // timestamp key and possibly other serverManagedRoles), but the created repository
 // result is only stored on local disk, not published to the server. To do that,
 // result is only stored on local disk, not published to the server. To do that,
 // use r.Publish() eventually.
 // use r.Publish() eventually.
-func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
-	privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID)
-	if err != nil {
-		return err
+func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...string) error {
+	privKeys := make([]data.PrivateKey, 0, len(rootKeyIDs))
+	for _, keyID := range rootKeyIDs {
+		privKey, _, err := r.CryptoService.GetPrivateKey(keyID)
+		if err != nil {
+			return err
+		}
+		privKeys = append(privKeys, privKey)
 	}
 	}
 
 
 	// currently we only support server managing timestamps and snapshots, and
 	// currently we only support server managing timestamps and snapshots, and
@@ -206,16 +210,20 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
 		}
 		}
 	}
 	}
 
 
-	rootKey, err := rootCertKey(r.gun, privKey)
-	if err != nil {
-		return err
+	rootKeys := make([]data.PublicKey, 0, len(privKeys))
+	for _, privKey := range privKeys {
+		rootKey, err := rootCertKey(r.gun, privKey)
+		if err != nil {
+			return err
+		}
+		rootKeys = append(rootKeys, rootKey)
 	}
 	}
 
 
 	var (
 	var (
 		rootRole = data.NewBaseRole(
 		rootRole = data.NewBaseRole(
 			data.CanonicalRootRole,
 			data.CanonicalRootRole,
 			notary.MinThreshold,
 			notary.MinThreshold,
-			rootKey,
+			rootKeys...,
 		)
 		)
 		timestampRole data.BaseRole
 		timestampRole data.BaseRole
 		snapshotRole  data.BaseRole
 		snapshotRole  data.BaseRole
@@ -271,7 +279,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
 
 
 	r.tufRepo = tuf.NewRepo(r.CryptoService)
 	r.tufRepo = tuf.NewRepo(r.CryptoService)
 
 
-	err = r.tufRepo.InitRoot(
+	err := r.tufRepo.InitRoot(
 		rootRole,
 		rootRole,
 		timestampRole,
 		timestampRole,
 		snapshotRole,
 		snapshotRole,
@@ -307,14 +315,14 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
 	for _, role := range roles {
 	for _, role := range roles {
 		// Ensure we can only add targets to the CanonicalTargetsRole,
 		// Ensure we can only add targets to the CanonicalTargetsRole,
 		// or a Delegation role (which is <CanonicalTargetsRole>/something else)
 		// or a Delegation role (which is <CanonicalTargetsRole>/something else)
-		if role != data.CanonicalTargetsRole && !data.IsDelegation(role) {
+		if role != data.CanonicalTargetsRole && !data.IsDelegation(role) && !data.IsWildDelegation(role) {
 			return data.ErrInvalidRole{
 			return data.ErrInvalidRole{
 				Role:   role,
 				Role:   role,
 				Reason: "cannot add targets to this role",
 				Reason: "cannot add targets to this role",
 			}
 			}
 		}
 		}
 
 
-		changes = append(changes, changelist.NewTufChange(
+		changes = append(changes, changelist.NewTUFChange(
 			c.Action(),
 			c.Action(),
 			role,
 			role,
 			c.Type(),
 			c.Type(),
@@ -352,7 +360,7 @@ func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {
 		return err
 		return err
 	}
 	}
 
 
-	template := changelist.NewTufChange(
+	template := changelist.NewTUFChange(
 		changelist.ActionCreate, "", changelist.TypeTargetsTarget,
 		changelist.ActionCreate, "", changelist.TypeTargetsTarget,
 		target.Name, metaJSON)
 		target.Name, metaJSON)
 	return addChange(cl, template, roles...)
 	return addChange(cl, template, roles...)
@@ -368,13 +376,14 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
 		return err
 		return err
 	}
 	}
 	logrus.Debugf("Removing target \"%s\"", targetName)
 	logrus.Debugf("Removing target \"%s\"", targetName)
-	template := changelist.NewTufChange(changelist.ActionDelete, "",
+	template := changelist.NewTUFChange(changelist.ActionDelete, "",
 		changelist.TypeTargetsTarget, targetName, nil)
 		changelist.TypeTargetsTarget, targetName, nil)
 	return addChange(cl, template, roles...)
 	return addChange(cl, template, roles...)
 }
 }
 
 
 // ListTargets lists all targets for the current repository. The list of
 // ListTargets lists all targets for the current repository. The list of
 // roles should be passed in order from highest to lowest priority.
 // roles should be passed in order from highest to lowest priority.
+//
 // IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
 // IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
 // "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
 // "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
 // its entries will be strictly shadowed by those in other parts of the "targets/a"
 // its entries will be strictly shadowed by those in other parts of the "targets/a"
@@ -402,11 +411,18 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
 				if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
 				if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
 					continue
 					continue
 				}
 				}
-				targets[targetName] =
-					&TargetWithRole{Target: Target{Name: targetName, Hashes: targetMeta.Hashes, Length: targetMeta.Length}, Role: validRole.Name}
+				targets[targetName] = &TargetWithRole{
+					Target: Target{
+						Name:   targetName,
+						Hashes: targetMeta.Hashes,
+						Length: targetMeta.Length,
+					},
+					Role: validRole.Name,
+				}
 			}
 			}
 			return nil
 			return nil
 		}
 		}
+
 		r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
 		r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
 	}
 	}
 
 
@@ -462,6 +478,62 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
 
 
 }
 }
 
 
+// TargetSignedStruct is a struct that contains a Target, the role it was found in, and the list of signatures for that role
+type TargetSignedStruct struct {
+	Role       data.DelegationRole
+	Target     Target
+	Signatures []data.Signature
+}
+
+// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
+// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
+// If given an empty string for a target name, it will return back all targets signed into the repository in every role
+func (r *NotaryRepository) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) {
+	if err := r.Update(false); err != nil {
+		return nil, err
+	}
+
+	var targetInfoList []TargetSignedStruct
+
+	// Define a visitor function to find the specified target
+	getAllTargetInfoByNameVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
+		if tgt == nil {
+			return nil
+		}
+		// We found a target and validated path compatibility in our walk,
+		// so add it to our list if we have a match
+		// if we have an empty name, add all targets, else check if we have it
+		var targetMetaToAdd data.Files
+		if name == "" {
+			targetMetaToAdd = tgt.Signed.Targets
+		} else {
+			if meta, ok := tgt.Signed.Targets[name]; ok {
+				targetMetaToAdd = data.Files{name: meta}
+			}
+		}
+
+		for targetName, resultMeta := range targetMetaToAdd {
+			targetInfo := TargetSignedStruct{
+				Role:       validRole,
+				Target:     Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length},
+				Signatures: tgt.Signatures,
+			}
+			targetInfoList = append(targetInfoList, targetInfo)
+		}
+		// continue walking to all child roles
+		return nil
+	}
+
+	// Check that we didn't error, and that we found the target at least once
+	if err := r.tufRepo.WalkTargets(name, "", getAllTargetInfoByNameVisitorFunc); err != nil {
+		return nil, err
+	}
+	if len(targetInfoList) == 0 {
+		return nil, fmt.Errorf("No valid trust data for %s", name)
+	}
+	return targetInfoList, nil
+}
+
 // GetChangelist returns the list of the repository's unpublished changes
 // GetChangelist returns the list of the repository's unpublished changes
 func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
 func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
 	changelistDir := filepath.Join(r.tufRepoPath, "changelist")
 	changelistDir := filepath.Join(r.tufRepoPath, "changelist")
@@ -567,19 +639,19 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
 		}
 		}
 	}
 	}
 	// apply the changelist to the repo
 	// apply the changelist to the repo
-	if err := applyChangelist(r.tufRepo, cl); err != nil {
+	if err := applyChangelist(r.tufRepo, r.invalid, cl); err != nil {
 		logrus.Debug("Error applying changelist")
 		logrus.Debug("Error applying changelist")
 		return err
 		return err
 	}
 	}
 
 
-	// these are the tuf files we will need to update, serialized as JSON before
+	// these are the TUF files we will need to update, serialized as JSON before
 	// we send anything to remote
 	// we send anything to remote
 	updatedFiles := make(map[string][]byte)
 	updatedFiles := make(map[string][]byte)
 
 
 	// check if our root file is nearing expiry or dirty. Resign if it is.  If
 	// check if our root file is nearing expiry or dirty. Resign if it is.  If
 	// root is not dirty but we are publishing for the first time, then just
 	// root is not dirty but we are publishing for the first time, then just
 	// publish the existing root we have.
 	// publish the existing root we have.
-	if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty {
+	if nearExpiry(r.tufRepo.Root.Signed.SignedCommon) || r.tufRepo.Root.Dirty {
 		rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole)
 		rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -635,7 +707,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
 		return err
 		return err
 	}
 	}
 
 
-	return remote.SetMultiMeta(updatedFiles)
+	return remote.SetMulti(updatedFiles)
 }
 }
 
 
 // bootstrapRepo loads the repository from the local file system (i.e.
 // bootstrapRepo loads the repository from the local file system (i.e.
@@ -649,7 +721,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
 	logrus.Debugf("Loading trusted collection.")
 	logrus.Debugf("Loading trusted collection.")
 
 
 	for _, role := range data.BaseRoles {
 	for _, role := range data.BaseRoles {
-		jsonBytes, err := r.fileStore.GetMeta(role, store.NoSizeLimit)
+		jsonBytes, err := r.fileStore.GetSized(role, store.NoSizeLimit)
 		if err != nil {
 		if err != nil {
 			if _, ok := err.(store.ErrMetaNotFound); ok &&
 			if _, ok := err.(store.ErrMetaNotFound); ok &&
 				// server snapshots are supported, and server timestamp management
 				// server snapshots are supported, and server timestamp management
@@ -665,7 +737,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
 		}
 		}
 	}
 	}
 
 
-	tufRepo, err := b.Finish()
+	tufRepo, _, err := b.Finish()
 	if err == nil {
 	if err == nil {
 		r.tufRepo = tufRepo
 		r.tufRepo = tufRepo
 	}
 	}
@@ -681,7 +753,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	err = r.fileStore.SetMeta(data.CanonicalRootRole, rootJSON)
+	err = r.fileStore.Set(data.CanonicalRootRole, rootJSON)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -702,7 +774,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
 	for role, blob := range targetsToSave {
 	for role, blob := range targetsToSave {
 		parentDir := filepath.Dir(role)
 		parentDir := filepath.Dir(role)
 		os.MkdirAll(parentDir, 0755)
 		os.MkdirAll(parentDir, 0755)
-		r.fileStore.SetMeta(role, blob)
+		r.fileStore.Set(role, blob)
 	}
 	}
 
 
 	if ignoreSnapshot {
 	if ignoreSnapshot {
@@ -714,7 +786,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
 		return err
 		return err
 	}
 	}
 
 
-	return r.fileStore.SetMeta(data.CanonicalSnapshotRole, snapshotJSON)
+	return r.fileStore.Set(data.CanonicalSnapshotRole, snapshotJSON)
 }
 }
 
 
 // returns a properly constructed ErrRepositoryNotExist error based on this
 // returns a properly constructed ErrRepositoryNotExist error based on this
@@ -738,7 +810,7 @@ func (r *NotaryRepository) Update(forWrite bool) error {
 		}
 		}
 		return err
 		return err
 	}
 	}
-	repo, err := c.Update()
+	repo, invalid, err := c.Update()
 	if err != nil {
 	if err != nil {
 		// notFound.Resource may include a checksum so when the role is root,
 		// notFound.Resource may include a checksum so when the role is root,
 		// it will be root or root.<checksum>. Therefore best we can
 		// it will be root or root.<checksum>. Therefore best we can
@@ -748,7 +820,11 @@ func (r *NotaryRepository) Update(forWrite bool) error {
 		}
 		}
 		return err
 		return err
 	}
 	}
+	// we can be assured if we are at this stage that the repo we built is good
+	// no need to test the following function call for an error as it will always be fine should the repo be good- it is!
 	r.tufRepo = repo
 	r.tufRepo = repo
+	r.invalid = invalid
+	warnRolesNearExpiry(repo)
 	return nil
 	return nil
 }
 }
 
 
@@ -759,16 +835,16 @@ func (r *NotaryRepository) Update(forWrite bool) error {
 // and return an error if the remote repository errors.
 // and return an error if the remote repository errors.
 //
 //
 // Populates a tuf.RepoBuilder with this root metadata (only use
 // Populates a tuf.RepoBuilder with this root metadata (only use
-// tufclient.Client.Update to load the rest).
+// TUFClient.Update to load the rest).
 //
 //
 // Fails if the remote server is reachable and does not know the repo
 // Fails if the remote server is reachable and does not know the repo
 // (i.e. before the first r.Publish()), in which case the error is
 // (i.e. before the first r.Publish()), in which case the error is
 // store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
 // store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
 // is not trusted.
 // is not trusted.
 //
 //
-// Returns a tufclient.Client for the remote server, which may not be actually
+// Returns a TUFClient for the remote server, which may not be actually
 // operational (if the URL is invalid but a root.json is cached).
 // operational (if the URL is invalid but a root.json is cached).
-func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) {
+func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*TUFClient, error) {
 	minVersion := 1
 	minVersion := 1
 	// the old root on disk should not be validated against any trust pinning configuration
 	// the old root on disk should not be validated against any trust pinning configuration
 	// because if we have an old root, it itself is the thing that pins trust
 	// because if we have an old root, it itself is the thing that pins trust
@@ -781,7 +857,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
 	// during update which will cause us to download a new root and perform a rotation.
 	// during update which will cause us to download a new root and perform a rotation.
 	// If we have an old root, and it's valid, then we overwrite the newBuilder to be one
 	// If we have an old root, and it's valid, then we overwrite the newBuilder to be one
 	// preloaded with the old root or one which uses the old root for trust bootstrapping.
 	// preloaded with the old root or one which uses the old root for trust bootstrapping.
-	if rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, store.NoSizeLimit); err == nil {
+	if rootJSON, err := r.fileStore.GetSized(data.CanonicalRootRole, store.NoSizeLimit); err == nil {
 		// if we can't load the cached root, fail hard because that is how we pin trust
 		// if we can't load the cached root, fail hard because that is how we pin trust
 		if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil {
 		if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil {
 			return nil, err
 			return nil, err
@@ -794,8 +870,9 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
 		if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil {
 		if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil {
 			// Ok, the old root is expired - we want to download a new one.  But we want to use the
 			// Ok, the old root is expired - we want to download a new one.  But we want to use the
 			// old root to verify the new root, so bootstrap a new builder with the old builder
 			// old root to verify the new root, so bootstrap a new builder with the old builder
+			// but use the trustpinning to validate the new root
 			minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
 			minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
-			newBuilder = oldBuilder.BootstrapNewBuilder()
+			newBuilder = oldBuilder.BootstrapNewBuilderWithNewTrustpin(r.trustPinning)
 		}
 		}
 	}
 	}
 
 
@@ -808,7 +885,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
 
 
 		// if remote store successfully set up, try and get root from remote
 		// if remote store successfully set up, try and get root from remote
 		// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
 		// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
-		tmpJSON, err := remote.GetMeta(data.CanonicalRootRole, store.NoSizeLimit)
+		tmpJSON, err := remote.GetSized(data.CanonicalRootRole, store.NoSizeLimit)
 		if err != nil {
 		if err != nil {
 			// we didn't have a root in cache and were unable to load one from
 			// we didn't have a root in cache and were unable to load one from
 			// the server. Nothing we can do but error.
 			// the server. Nothing we can do but error.
@@ -821,7 +898,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
 				return nil, err
 				return nil, err
 			}
 			}
 
 
-			err = r.fileStore.SetMeta(data.CanonicalRootRole, tmpJSON)
+			err = r.fileStore.Set(data.CanonicalRootRole, tmpJSON)
 			if err != nil {
 			if err != nil {
 				// if we can't write cache we should still continue, just log error
 				// if we can't write cache we should still continue, just log error
 				logrus.Errorf("could not save root to cache: %s", err.Error())
 				logrus.Errorf("could not save root to cache: %s", err.Error())
@@ -835,7 +912,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
 		return nil, ErrRepoNotInitialized{}
 		return nil, ErrRepoNotInitialized{}
 	}
 	}
 
 
-	return tufclient.NewClient(oldBuilder, newBuilder, remote, r.fileStore), nil
+	return NewTUFClient(oldBuilder, newBuilder, remote, r.fileStore), nil
 }
 }
 
 
 // RotateKey removes all existing keys associated with the role, and either
 // RotateKey removes all existing keys associated with the role, and either
@@ -864,7 +941,7 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
 	)
 	)
 	switch serverManagesKey {
 	switch serverManagesKey {
 	case true:
 	case true:
-		pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
+		pubKey, err = rotateRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
 		errFmtMsg = "unable to rotate remote key: %s"
 		errFmtMsg = "unable to rotate remote key: %s"
 	default:
 	default:
 		pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey)
 		pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey)
@@ -897,7 +974,7 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
 func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
 func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
 	kl := make(data.KeyList, 0, 1)
 	kl := make(data.KeyList, 0, 1)
 	kl = append(kl, key)
 	kl = append(kl, key)
-	meta := changelist.TufRootData{
+	meta := changelist.TUFRootData{
 		RoleName: role,
 		RoleName: role,
 		Keys:     kl,
 		Keys:     kl,
 	}
 	}
@@ -906,7 +983,7 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, act
 		return err
 		return err
 	}
 	}
 
 
-	c := changelist.NewTufChange(
+	c := changelist.NewTUFChange(
 		action,
 		action,
 		changelist.ScopeRoot,
 		changelist.ScopeRoot,
 		changelist.TypeRootRole,
 		changelist.TypeRootRole,
@@ -917,11 +994,21 @@ func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, act
 }
 }
 
 
 // DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side
 // DeleteTrustData removes the trust data stored for this repo in the TUF cache on the client side
-func (r *NotaryRepository) DeleteTrustData() error {
-	// Clear TUF files and cache
-	if err := r.fileStore.RemoveAll(); err != nil {
+// Note that we will not delete any private key material from local storage
+func (r *NotaryRepository) DeleteTrustData(deleteRemote bool) error {
+	// Remove the tufRepoPath directory, which includes local TUF metadata files and changelist information
+	if err := os.RemoveAll(r.tufRepoPath); err != nil {
 		return fmt.Errorf("error clearing TUF repo data: %v", err)
 		return fmt.Errorf("error clearing TUF repo data: %v", err)
 	}
 	}
-	r.tufRepo = tuf.NewRepo(nil)
+	// Note that this will require admin permission in this NotaryRepository's roundtripper
+	if deleteRemote {
+		remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
+		if err != nil {
+			return err
+		}
+		if err := remote.RemoveAll(); err != nil {
+			return err
+		}
+	}
 	return nil
 	return nil
 }
 }

+ 25 - 20
vendor/src/github.com/docker/notary/client/delegations.go

@@ -8,8 +8,8 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/notary"
 	"github.com/docker/notary"
 	"github.com/docker/notary/client/changelist"
 	"github.com/docker/notary/client/changelist"
+	store "github.com/docker/notary/storage"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
-	"github.com/docker/notary/tuf/store"
 	"github.com/docker/notary/tuf/utils"
 	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
@@ -50,7 +50,7 @@ func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys
 		name, notary.MinThreshold, len(delegationKeys))
 		name, notary.MinThreshold, len(delegationKeys))
 
 
 	// Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment.
 	// Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment.
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
+	tdJSON, err := json.Marshal(&changelist.TUFDelegation{
 		NewThreshold: notary.MinThreshold,
 		NewThreshold: notary.MinThreshold,
 		AddKeys:      data.KeyList(delegationKeys),
 		AddKeys:      data.KeyList(delegationKeys),
 	})
 	})
@@ -78,7 +78,7 @@ func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error
 
 
 	logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
 	logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
 
 
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
+	tdJSON, err := json.Marshal(&changelist.TUFDelegation{
 		AddPaths: paths,
 		AddPaths: paths,
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -141,7 +141,7 @@ func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) er
 
 
 	logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
 	logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
 
 
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
+	tdJSON, err := json.Marshal(&changelist.TUFDelegation{
 		RemovePaths: paths,
 		RemovePaths: paths,
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -155,9 +155,11 @@ func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) er
 // RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
 // RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
 // When this changelist is applied, if the specified keys are the only keys left in the role,
 // When this changelist is applied, if the specified keys are the only keys left in the role,
 // the role itself will be deleted in its entirety.
 // the role itself will be deleted in its entirety.
+// It can also delete a key from all delegations under a parent using a name
+// with a wildcard at the end.
 func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error {
 func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error {
 
 
-	if !data.IsDelegation(name) {
+	if !data.IsDelegation(name) && !data.IsWildDelegation(name) {
 		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
 		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
 	}
 	}
 
 
@@ -169,7 +171,7 @@ func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) er
 
 
 	logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
 	logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
 
 
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
+	tdJSON, err := json.Marshal(&changelist.TUFDelegation{
 		RemoveKeys: keyIDs,
 		RemoveKeys: keyIDs,
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -195,7 +197,7 @@ func (r *NotaryRepository) ClearDelegationPaths(name string) error {
 
 
 	logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
 	logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
 
 
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
+	tdJSON, err := json.Marshal(&changelist.TUFDelegation{
 		ClearAllPaths: true,
 		ClearAllPaths: true,
 	})
 	})
 	if err != nil {
 	if err != nil {
@@ -206,8 +208,8 @@ func (r *NotaryRepository) ClearDelegationPaths(name string) error {
 	return addChange(cl, template, name)
 	return addChange(cl, template, name)
 }
 }
 
 
-func newUpdateDelegationChange(name string, content []byte) *changelist.TufChange {
-	return changelist.NewTufChange(
+func newUpdateDelegationChange(name string, content []byte) *changelist.TUFChange {
+	return changelist.NewTUFChange(
 		changelist.ActionUpdate,
 		changelist.ActionUpdate,
 		name,
 		name,
 		changelist.TypeTargetsDelegation,
 		changelist.TypeTargetsDelegation,
@@ -216,8 +218,8 @@ func newUpdateDelegationChange(name string, content []byte) *changelist.TufChang
 	)
 	)
 }
 }
 
 
-func newCreateDelegationChange(name string, content []byte) *changelist.TufChange {
-	return changelist.NewTufChange(
+func newCreateDelegationChange(name string, content []byte) *changelist.TUFChange {
+	return changelist.NewTUFChange(
 		changelist.ActionCreate,
 		changelist.ActionCreate,
 		name,
 		name,
 		changelist.TypeTargetsDelegation,
 		changelist.TypeTargetsDelegation,
@@ -226,8 +228,8 @@ func newCreateDelegationChange(name string, content []byte) *changelist.TufChang
 	)
 	)
 }
 }
 
 
-func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange {
-	return changelist.NewTufChange(
+func newDeleteDelegationChange(name string, content []byte) *changelist.TUFChange {
+	return changelist.NewTUFChange(
 		changelist.ActionDelete,
 		changelist.ActionDelete,
 		name,
 		name,
 		changelist.TypeTargetsDelegation,
 		changelist.TypeTargetsDelegation,
@@ -238,7 +240,7 @@ func newDeleteDelegationChange(name string, content []byte) *changelist.TufChang
 
 
 // GetDelegationRoles returns the keys and roles of the repository's delegations
 // GetDelegationRoles returns the keys and roles of the repository's delegations
 // Also converts key IDs to canonical key IDs to keep consistent with signing prompts
 // Also converts key IDs to canonical key IDs to keep consistent with signing prompts
-func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
+func (r *NotaryRepository) GetDelegationRoles() ([]data.Role, error) {
 	// Update state of the repo to latest
 	// Update state of the repo to latest
 	if err := r.Update(false); err != nil {
 	if err := r.Update(false); err != nil {
 		return nil, err
 		return nil, err
@@ -251,7 +253,7 @@ func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
 	}
 	}
 
 
 	// make a copy for traversing nested delegations
 	// make a copy for traversing nested delegations
-	allDelegations := []*data.Role{}
+	allDelegations := []data.Role{}
 
 
 	// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
 	// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
 	delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
 	delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
@@ -271,20 +273,23 @@ func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
 	return allDelegations, nil
 	return allDelegations, nil
 }
 }
 
 
-func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) {
-	canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles))
-	copy(canonicalDelegations, delegationInfo.Roles)
+func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]data.Role, error) {
+	canonicalDelegations := make([]data.Role, len(delegationInfo.Roles))
+	// Do a copy by value to ensure local delegation metadata is untouched
+	for idx, origRole := range delegationInfo.Roles {
+		canonicalDelegations[idx] = *origRole
+	}
 	delegationKeys := delegationInfo.Keys
 	delegationKeys := delegationInfo.Keys
 	for i, delegation := range canonicalDelegations {
 	for i, delegation := range canonicalDelegations {
 		canonicalKeyIDs := []string{}
 		canonicalKeyIDs := []string{}
 		for _, keyID := range delegation.KeyIDs {
 		for _, keyID := range delegation.KeyIDs {
 			pubKey, ok := delegationKeys[keyID]
 			pubKey, ok := delegationKeys[keyID]
 			if !ok {
 			if !ok {
-				return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
+				return []data.Role{}, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
 			}
 			}
 			canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
 			canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
 			if err != nil {
 			if err != nil {
-				return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
+				return []data.Role{}, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
 			}
 			}
 			canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
 			canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
 		}
 		}

+ 63 - 22
vendor/src/github.com/docker/notary/client/helpers.go

@@ -4,14 +4,13 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
-	"strings"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/notary/client/changelist"
 	"github.com/docker/notary/client/changelist"
-	tuf "github.com/docker/notary/tuf"
+	store "github.com/docker/notary/storage"
+	"github.com/docker/notary/tuf"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
-	"github.com/docker/notary/tuf/store"
 	"github.com/docker/notary/tuf/utils"
 	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
@@ -30,7 +29,7 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor
 	return s, err
 	return s, err
 }
 }
 
 
-func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error {
+func applyChangelist(repo *tuf.Repo, invalid *tuf.Repo, cl changelist.Changelist) error {
 	it, err := cl.NewIterator()
 	it, err := cl.NewIterator()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -41,30 +40,33 @@ func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		isDel := data.IsDelegation(c.Scope())
+		isDel := data.IsDelegation(c.Scope()) || data.IsWildDelegation(c.Scope())
 		switch {
 		switch {
 		case c.Scope() == changelist.ScopeTargets || isDel:
 		case c.Scope() == changelist.ScopeTargets || isDel:
-			err = applyTargetsChange(repo, c)
+			err = applyTargetsChange(repo, invalid, c)
 		case c.Scope() == changelist.ScopeRoot:
 		case c.Scope() == changelist.ScopeRoot:
 			err = applyRootChange(repo, c)
 			err = applyRootChange(repo, c)
 		default:
 		default:
-			logrus.Debug("scope not supported: ", c.Scope())
+			return fmt.Errorf("scope not supported: %s", c.Scope())
 		}
 		}
-		index++
 		if err != nil {
 		if err != nil {
+			logrus.Debugf("error attempting to apply change #%d: %s, on scope: %s path: %s type: %s", index, c.Action(), c.Scope(), c.Path(), c.Type())
 			return err
 			return err
 		}
 		}
+		index++
 	}
 	}
 	logrus.Debugf("applied %d change(s)", index)
 	logrus.Debugf("applied %d change(s)", index)
 	return nil
 	return nil
 }
 }
 
 
-func applyTargetsChange(repo *tuf.Repo, c changelist.Change) error {
+func applyTargetsChange(repo *tuf.Repo, invalid *tuf.Repo, c changelist.Change) error {
 	switch c.Type() {
 	switch c.Type() {
 	case changelist.TypeTargetsTarget:
 	case changelist.TypeTargetsTarget:
 		return changeTargetMeta(repo, c)
 		return changeTargetMeta(repo, c)
 	case changelist.TypeTargetsDelegation:
 	case changelist.TypeTargetsDelegation:
 		return changeTargetsDelegation(repo, c)
 		return changeTargetsDelegation(repo, c)
+	case changelist.TypeWitness:
+		return witnessTargets(repo, invalid, c.Scope())
 	default:
 	default:
 		return fmt.Errorf("only target meta and delegations changes supported")
 		return fmt.Errorf("only target meta and delegations changes supported")
 	}
 	}
@@ -73,7 +75,7 @@ func applyTargetsChange(repo *tuf.Repo, c changelist.Change) error {
 func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
 func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
 	switch c.Action() {
 	switch c.Action() {
 	case changelist.ActionCreate:
 	case changelist.ActionCreate:
-		td := changelist.TufDelegation{}
+		td := changelist.TUFDelegation{}
 		err := json.Unmarshal(c.Content(), &td)
 		err := json.Unmarshal(c.Content(), &td)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -87,11 +89,15 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
 		}
 		}
 		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
 		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
 	case changelist.ActionUpdate:
 	case changelist.ActionUpdate:
-		td := changelist.TufDelegation{}
+		td := changelist.TUFDelegation{}
 		err := json.Unmarshal(c.Content(), &td)
 		err := json.Unmarshal(c.Content(), &td)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
+		if data.IsWildDelegation(c.Scope()) {
+			return repo.PurgeDelegationKeys(c.Scope(), td.RemoveKeys)
+		}
+
 		delgRole, err := repo.GetDelegationRole(c.Scope())
 		delgRole, err := repo.GetDelegationRole(c.Scope())
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -112,10 +118,6 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
 			removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
 			removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
 		}
 		}
 
 
-		// If we specify the only keys left delete the role, else just delete specified keys
-		if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 {
-			return repo.DeleteDelegation(c.Scope())
-		}
 		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
 		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -155,7 +157,7 @@ func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
 		}
 		}
 
 
 	default:
 	default:
-		logrus.Debug("action not yet supported: ", c.Action())
+		err = fmt.Errorf("action not yet supported: %s", c.Action())
 	}
 	}
 	return err
 	return err
 }
 }
@@ -166,7 +168,7 @@ func applyRootChange(repo *tuf.Repo, c changelist.Change) error {
 	case changelist.TypeRootRole:
 	case changelist.TypeRootRole:
 		err = applyRootRoleChange(repo, c)
 		err = applyRootRoleChange(repo, c)
 	default:
 	default:
-		logrus.Debug("type of root change not yet supported: ", c.Type())
+		err = fmt.Errorf("type of root change not yet supported: %s", c.Type())
 	}
 	}
 	return err // might be nil
 	return err // might be nil
 }
 }
@@ -175,7 +177,7 @@ func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error {
 	switch c.Action() {
 	switch c.Action() {
 	case changelist.ActionCreate:
 	case changelist.ActionCreate:
 		// replaces all keys for a role
 		// replaces all keys for a role
-		d := &changelist.TufRootData{}
+		d := &changelist.TUFRootData{}
 		err := json.Unmarshal(c.Content(), d)
 		err := json.Unmarshal(c.Content(), d)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -185,14 +187,34 @@ func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error {
 			return err
 			return err
 		}
 		}
 	default:
 	default:
-		logrus.Debug("action not yet supported for root: ", c.Action())
+		return fmt.Errorf("action not yet supported for root: %s", c.Action())
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func nearExpiry(r *data.SignedRoot) bool {
+func nearExpiry(r data.SignedCommon) bool {
 	plus6mo := time.Now().AddDate(0, 6, 0)
 	plus6mo := time.Now().AddDate(0, 6, 0)
-	return r.Signed.Expires.Before(plus6mo)
+	return r.Expires.Before(plus6mo)
+}
+
+func warnRolesNearExpiry(r *tuf.Repo) {
+	//get every role and its respective signed common and call nearExpiry on it
+	//Root check
+	if nearExpiry(r.Root.Signed.SignedCommon) {
+		logrus.Warn("root is nearing expiry, you should re-sign the role metadata")
+	}
+	//Targets and delegations check
+	for role, signedTOrD := range r.Targets {
+		//signedTOrD is of type *data.SignedTargets
+		if nearExpiry(signedTOrD.Signed.SignedCommon) {
+			logrus.Warn(role, " metadata is nearing expiry, you should re-sign the role metadata")
+		}
+	}
+	//Snapshot check
+	if nearExpiry(r.Snapshot.Signed.SignedCommon) {
+		logrus.Warn("snapshot is nearing expiry, you should re-sign the role metadata")
+	}
+	//do not need to worry about Timestamp, notary signer will re-sign with the timestamp key
 }
 }
 
 
 // Fetches a public key from a remote store, given a gun and role
 // Fetches a public key from a remote store, given a gun and role
@@ -214,7 +236,26 @@ func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey,
 	return pubKey, nil
 	return pubKey, nil
 }
 }
 
 
-// signs and serializes the metadata for a canonical role in a tuf repo to JSON
+// Rotates a private key in a remote store and returns the public key component
+func rotateRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, error) {
+	remote, err := getRemoteStore(url, gun, rt)
+	if err != nil {
+		return nil, err
+	}
+	rawPubKey, err := remote.RotateKey(role)
+	if err != nil {
+		return nil, err
+	}
+
+	pubKey, err := data.UnmarshalPublicKey(rawPubKey)
+	if err != nil {
+		return nil, err
+	}
+
+	return pubKey, nil
+}
+
+// signs and serializes the metadata for a canonical role in a TUF repo to JSON
 func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
 func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
 	var s *data.Signed
 	var s *data.Signed
 	switch {
 	switch {

+ 2 - 2
vendor/src/github.com/docker/notary/client/repo.go

@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 
 
-	"github.com/docker/notary/passphrase"
+	"github.com/docker/notary"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/trustpinning"
 )
 )
@@ -16,7 +16,7 @@ import (
 // (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling
 // (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling
 // docker content trust).
 // docker content trust).
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
-	retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
+	retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) (
 	*NotaryRepository, error) {
 	*NotaryRepository, error) {
 
 
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)

+ 2 - 2
vendor/src/github.com/docker/notary/client/repo_pkcs11.go

@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"net/http"
 	"net/http"
 
 
-	"github.com/docker/notary/passphrase"
+	"github.com/docker/notary"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager/yubikey"
 	"github.com/docker/notary/trustmanager/yubikey"
 	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/trustpinning"
@@ -16,7 +16,7 @@ import (
 // It takes the base directory under where all the trust files will be stored
 // It takes the base directory under where all the trust files will be stored
 // (usually ~/.docker/trust/).
 // (usually ~/.docker/trust/).
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
-	retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
+	retriever notary.PassRetriever, trustPinning trustpinning.TrustPinConfig) (
 	*NotaryRepository, error) {
 	*NotaryRepository, error) {
 
 
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)

+ 54 - 44
vendor/src/github.com/docker/notary/tuf/client/client.go → vendor/src/github.com/docker/notary/client/tufclient.go

@@ -5,22 +5,23 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/notary"
 	"github.com/docker/notary"
-	tuf "github.com/docker/notary/tuf"
+	store "github.com/docker/notary/storage"
+	"github.com/docker/notary/tuf"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
-	"github.com/docker/notary/tuf/store"
+	"github.com/docker/notary/tuf/signed"
 )
 )
 
 
-// Client is a usability wrapper around a raw TUF repo
-type Client struct {
+// TUFClient is a usability wrapper around a raw TUF repo
+type TUFClient struct {
 	remote     store.RemoteStore
 	remote     store.RemoteStore
 	cache      store.MetadataStore
 	cache      store.MetadataStore
 	oldBuilder tuf.RepoBuilder
 	oldBuilder tuf.RepoBuilder
 	newBuilder tuf.RepoBuilder
 	newBuilder tuf.RepoBuilder
 }
 }
 
 
-// NewClient initialized a Client with the given repo, remote source of content, and cache
-func NewClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *Client {
-	return &Client{
+// NewTUFClient initialized a TUFClient with the given repo, remote source of content, and cache
+func NewTUFClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *TUFClient {
+	return &TUFClient{
 		oldBuilder: oldBuilder,
 		oldBuilder: oldBuilder,
 		newBuilder: newBuilder,
 		newBuilder: newBuilder,
 		remote:     remote,
 		remote:     remote,
@@ -29,7 +30,7 @@ func NewClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore,
 }
 }
 
 
 // Update performs an update to the TUF repo as defined by the TUF spec
 // Update performs an update to the TUF repo as defined by the TUF spec
-func (c *Client) Update() (*tuf.Repo, error) {
+func (c *TUFClient) Update() (*tuf.Repo, *tuf.Repo, error) {
 	// 1. Get timestamp
 	// 1. Get timestamp
 	//   a. If timestamp error (verification, expired, etc...) download new root and return to 1.
 	//   a. If timestamp error (verification, expired, etc...) download new root and return to 1.
 	// 2. Check if local snapshot is up to date
 	// 2. Check if local snapshot is up to date
@@ -48,19 +49,19 @@ func (c *Client) Update() (*tuf.Repo, error) {
 
 
 		if err := c.downloadRoot(); err != nil {
 		if err := c.downloadRoot(); err != nil {
 			logrus.Debug("Client Update (Root):", err)
 			logrus.Debug("Client Update (Root):", err)
-			return nil, err
+			return nil, nil, err
 		}
 		}
 		// If we error again, we now have the latest root and just want to fail
 		// If we error again, we now have the latest root and just want to fail
 		// out as there's no expectation the problem can be resolved automatically
 		// out as there's no expectation the problem can be resolved automatically
 		logrus.Debug("retrying TUF client update")
 		logrus.Debug("retrying TUF client update")
 		if err := c.update(); err != nil {
 		if err := c.update(); err != nil {
-			return nil, err
+			return nil, nil, err
 		}
 		}
 	}
 	}
 	return c.newBuilder.Finish()
 	return c.newBuilder.Finish()
 }
 }
 
 
-func (c *Client) update() error {
+func (c *TUFClient) update() error {
 	if err := c.downloadTimestamp(); err != nil {
 	if err := c.downloadTimestamp(); err != nil {
 		logrus.Debugf("Client Update (Timestamp): %s", err.Error())
 		logrus.Debugf("Client Update (Timestamp): %s", err.Error())
 		return err
 		return err
@@ -78,7 +79,7 @@ func (c *Client) update() error {
 }
 }
 
 
 // downloadRoot is responsible for downloading the root.json
 // downloadRoot is responsible for downloading the root.json
-func (c *Client) downloadRoot() error {
+func (c *TUFClient) downloadRoot() error {
 	role := data.CanonicalRootRole
 	role := data.CanonicalRootRole
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
 
 
@@ -88,7 +89,7 @@ func (c *Client) downloadRoot() error {
 		logrus.Debugf("Loading root with no expected checksum")
 		logrus.Debugf("Loading root with no expected checksum")
 
 
 		// get the cached root, if it exists, just for version checking
 		// get the cached root, if it exists, just for version checking
-		cachedRoot, _ := c.cache.GetMeta(role, -1)
+		cachedRoot, _ := c.cache.GetSized(role, -1)
 		// prefer to download a new root
 		// prefer to download a new root
 		_, remoteErr := c.tryLoadRemote(consistentInfo, cachedRoot)
 		_, remoteErr := c.tryLoadRemote(consistentInfo, cachedRoot)
 		return remoteErr
 		return remoteErr
@@ -101,36 +102,43 @@ func (c *Client) downloadRoot() error {
 // downloadTimestamp is responsible for downloading the timestamp.json
 // downloadTimestamp is responsible for downloading the timestamp.json
 // Timestamps are special in that we ALWAYS attempt to download and only
 // Timestamps are special in that we ALWAYS attempt to download and only
 // use cache if the download fails (and the cache is still valid).
 // use cache if the download fails (and the cache is still valid).
-func (c *Client) downloadTimestamp() error {
+func (c *TUFClient) downloadTimestamp() error {
 	logrus.Debug("Loading timestamp...")
 	logrus.Debug("Loading timestamp...")
 	role := data.CanonicalTimestampRole
 	role := data.CanonicalTimestampRole
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
 
 
-	// get the cached timestamp, if it exists
-	cachedTS, cachedErr := c.cache.GetMeta(role, notary.MaxTimestampSize)
-	// always get the remote timestamp, since it supercedes the local one
+	// always get the remote timestamp, since it supersedes the local one
+	cachedTS, cachedErr := c.cache.GetSized(role, notary.MaxTimestampSize)
 	_, remoteErr := c.tryLoadRemote(consistentInfo, cachedTS)
 	_, remoteErr := c.tryLoadRemote(consistentInfo, cachedTS)
 
 
-	switch {
-	case remoteErr == nil:
+	// check that there was no remote error, or if there was a network problem
+	// If there was a validation error, we should error out so we can download a new root or fail the update
+	switch remoteErr.(type) {
+	case nil:
 		return nil
 		return nil
-	case cachedErr == nil:
-		logrus.Debug(remoteErr.Error())
-		logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
-
-		err := c.newBuilder.Load(role, cachedTS, 1, false)
-		if err == nil {
-			logrus.Debug("successfully verified cached timestamp")
-		}
-		return err
+	case store.ErrMetaNotFound, store.ErrServerUnavailable, store.ErrOffline, store.NetworkError:
+		break
 	default:
 	default:
+		return remoteErr
+	}
+
+	// since it was a network error: get the cached timestamp, if it exists
+	if cachedErr != nil {
 		logrus.Debug("no cached or remote timestamp available")
 		logrus.Debug("no cached or remote timestamp available")
 		return remoteErr
 		return remoteErr
 	}
 	}
+
+	logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
+	err := c.newBuilder.Load(role, cachedTS, 1, false)
+	if err == nil {
+		logrus.Debug("successfully verified cached timestamp")
+	}
+	return err
+
 }
 }
 
 
 // downloadSnapshot is responsible for downloading the snapshot.json
 // downloadSnapshot is responsible for downloading the snapshot.json
-func (c *Client) downloadSnapshot() error {
+func (c *TUFClient) downloadSnapshot() error {
 	logrus.Debug("Loading snapshot...")
 	logrus.Debug("Loading snapshot...")
 	role := data.CanonicalSnapshotRole
 	role := data.CanonicalSnapshotRole
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
 	consistentInfo := c.newBuilder.GetConsistentInfo(role)
@@ -142,11 +150,12 @@ func (c *Client) downloadSnapshot() error {
 // downloadTargets downloads all targets and delegated targets for the repository.
 // downloadTargets downloads all targets and delegated targets for the repository.
 // It uses a pre-order tree traversal as it's necessary to download parents first
 // It uses a pre-order tree traversal as it's necessary to download parents first
 // to obtain the keys to validate children.
 // to obtain the keys to validate children.
-func (c *Client) downloadTargets() error {
+func (c *TUFClient) downloadTargets() error {
 	toDownload := []data.DelegationRole{{
 	toDownload := []data.DelegationRole{{
 		BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole},
 		BaseRole: data.BaseRole{Name: data.CanonicalTargetsRole},
 		Paths:    []string{""},
 		Paths:    []string{""},
 	}}
 	}}
+
 	for len(toDownload) > 0 {
 	for len(toDownload) > 0 {
 		role := toDownload[0]
 		role := toDownload[0]
 		toDownload = toDownload[1:]
 		toDownload = toDownload[1:]
@@ -158,21 +167,23 @@ func (c *Client) downloadTargets() error {
 		}
 		}
 
 
 		children, err := c.getTargetsFile(role, consistentInfo)
 		children, err := c.getTargetsFile(role, consistentInfo)
-		if err != nil {
-			if _, ok := err.(data.ErrMissingMeta); ok && role.Name != data.CanonicalTargetsRole {
-				// if the role meta hasn't been published,
-				// that's ok, continue
-				continue
+		switch err.(type) {
+		case signed.ErrExpired, signed.ErrRoleThreshold:
+			if role.Name == data.CanonicalTargetsRole {
+				return err
 			}
 			}
-			logrus.Debugf("Error getting %s: %s", role.Name, err)
+			logrus.Warnf("Error getting %s: %s", role.Name, err)
+			break
+		case nil:
+			toDownload = append(children, toDownload...)
+		default:
 			return err
 			return err
 		}
 		}
-		toDownload = append(children, toDownload...)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (c Client) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) {
+func (c TUFClient) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo) ([]data.DelegationRole, error) {
 	logrus.Debugf("Loading %s...", role.Name)
 	logrus.Debugf("Loading %s...", role.Name)
 	tgs := &data.SignedTargets{}
 	tgs := &data.SignedTargets{}
 
 
@@ -187,8 +198,8 @@ func (c Client) getTargetsFile(role data.DelegationRole, ci tuf.ConsistentInfo)
 	return tgs.GetValidDelegations(role), nil
 	return tgs.GetValidDelegations(role), nil
 }
 }
 
 
-func (c *Client) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) {
-	cachedTS, err := c.cache.GetMeta(consistentInfo.RoleName, consistentInfo.Length())
+func (c *TUFClient) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]byte, error) {
+	cachedTS, err := c.cache.GetSized(consistentInfo.RoleName, consistentInfo.Length())
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName)
 		logrus.Debugf("no %s in cache, must download", consistentInfo.RoleName)
 		return c.tryLoadRemote(consistentInfo, nil)
 		return c.tryLoadRemote(consistentInfo, nil)
@@ -203,9 +214,9 @@ func (c *Client) tryLoadCacheThenRemote(consistentInfo tuf.ConsistentInfo) ([]by
 	return c.tryLoadRemote(consistentInfo, cachedTS)
 	return c.tryLoadRemote(consistentInfo, cachedTS)
 }
 }
 
 
-func (c *Client) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) {
+func (c *TUFClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([]byte, error) {
 	consistentName := consistentInfo.ConsistentName()
 	consistentName := consistentInfo.ConsistentName()
-	raw, err := c.remote.GetMeta(consistentName, consistentInfo.Length())
+	raw, err := c.remote.GetSized(consistentName, consistentInfo.Length())
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("error downloading %s: %s", consistentName, err)
 		logrus.Debugf("error downloading %s: %s", consistentName, err)
 		return old, err
 		return old, err
@@ -216,13 +227,12 @@ func (c *Client) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte) ([
 	// will be 1
 	// will be 1
 	c.oldBuilder.Load(consistentInfo.RoleName, old, 1, true)
 	c.oldBuilder.Load(consistentInfo.RoleName, old, 1, true)
 	minVersion := c.oldBuilder.GetLoadedVersion(consistentInfo.RoleName)
 	minVersion := c.oldBuilder.GetLoadedVersion(consistentInfo.RoleName)
-
 	if err := c.newBuilder.Load(consistentInfo.RoleName, raw, minVersion, false); err != nil {
 	if err := c.newBuilder.Load(consistentInfo.RoleName, raw, minVersion, false); err != nil {
 		logrus.Debugf("downloaded %s is invalid: %s", consistentName, err)
 		logrus.Debugf("downloaded %s is invalid: %s", consistentName, err)
 		return raw, err
 		return raw, err
 	}
 	}
 	logrus.Debugf("successfully verified downloaded %s", consistentName)
 	logrus.Debugf("successfully verified downloaded %s", consistentName)
-	if err := c.cache.SetMeta(consistentInfo.RoleName, raw); err != nil {
+	if err := c.cache.Set(consistentInfo.RoleName, raw); err != nil {
 		logrus.Debugf("Unable to write %s to cache: %s", consistentInfo.RoleName, err)
 		logrus.Debugf("Unable to write %s to cache: %s", consistentInfo.RoleName, err)
 	}
 	}
 	return raw, nil
 	return raw, nil

+ 69 - 0
vendor/src/github.com/docker/notary/client/witness.go

@@ -0,0 +1,69 @@
+package client
+
+import (
+	"path/filepath"
+
+	"github.com/docker/notary/client/changelist"
+	"github.com/docker/notary/tuf"
+	"github.com/docker/notary/tuf/data"
+)
+
+// Witness creates change objects to witness (i.e. re-sign) the given
+// roles on the next publish. One change is created per role
+func (r *NotaryRepository) Witness(roles ...string) ([]string, error) {
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
+	if err != nil {
+		return nil, err
+	}
+	defer cl.Close()
+
+	successful := make([]string, 0, len(roles))
+	for _, role := range roles {
+		// scope is role
+		c := changelist.NewTUFChange(
+			changelist.ActionUpdate,
+			role,
+			changelist.TypeWitness,
+			"",
+			nil,
+		)
+		err = cl.Add(c)
+		if err != nil {
+			break
+		}
+		successful = append(successful, role)
+	}
+	return successful, err
+}
+
+func witnessTargets(repo *tuf.Repo, invalid *tuf.Repo, role string) error {
+	if r, ok := repo.Targets[role]; ok {
+		// role is already valid, mark for re-signing/updating
+		r.Dirty = true
+		return nil
+	}
+
+	if roleObj, err := repo.GetDelegationRole(role); err == nil && invalid != nil {
+		// A role with a threshold > len(keys) is technically invalid, but we let it build in the builder because
+		// we want to be able to download the role (which may still have targets on it), add more keys, and then
+		// witness the role, thus bringing it back to valid.  However, if no keys have been added before witnessing,
+		// then it is still an invalid role, and can't be witnessed because nothing can bring it back to valid.
+		if roleObj.Threshold > len(roleObj.Keys) {
+			return data.ErrInvalidRole{
+				Role:   role,
+				Reason: "role does not specify enough valid signing keys to meet its required threshold",
+			}
+		}
+		if r, ok := invalid.Targets[role]; ok {
+			// role is recognized but invalid, move to valid data and mark for re-signing
+			repo.Targets[role] = r
+			r.Dirty = true
+			return nil
+		}
+	}
+	// role isn't recognized, even as invalid
+	return data.ErrInvalidRole{
+		Role:   role,
+		Reason: "this role is not known",
+	}
+}

+ 4 - 0
vendor/src/github.com/docker/notary/codecov.yml

@@ -3,16 +3,20 @@ codecov:
     # 2 builds on circleci, 1 jenkins build
     # 2 builds on circleci, 1 jenkins build
     after_n_builds: 3
     after_n_builds: 3
 coverage:
 coverage:
+  range: "50...100"
   status:
   status:
     # project will give us the diff in the total code coverage between a commit
     # project will give us the diff in the total code coverage between a commit
     # and its parent
     # and its parent
     project:
     project:
       default:
       default:
         target: auto
         target: auto
+        threshold: "0.05%"
     # patch would give us the code coverage of the diff only
     # patch would give us the code coverage of the diff only
     patch: false
     patch: false
     # changes tells us if there are unexpected code coverage changes in other files
     # changes tells us if there are unexpected code coverage changes in other files
     # which were not changed by the diff
     # which were not changed by the diff
     changes: false
     changes: false
+  ignore:  # ignore testutils for coverage
+    - "tuf/testutils/*"
 comment: off
 comment: off
 
 

+ 5 - 3
vendor/src/github.com/docker/notary/const.go

@@ -1,8 +1,6 @@
 package notary
 package notary
 
 
-import (
-	"time"
-)
+import "time"
 
 
 // application wide constants
 // application wide constants
 const (
 const (
@@ -34,6 +32,8 @@ const (
 	RootKeysSubdir = "root_keys"
 	RootKeysSubdir = "root_keys"
 	// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
 	// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
 	NonRootKeysSubdir = "tuf_keys"
 	NonRootKeysSubdir = "tuf_keys"
+	// KeyExtension is the file extension to use for private key files
+	KeyExtension = "key"
 
 
 	// Day is a duration of one day
 	// Day is a duration of one day
 	Day  = 24 * time.Hour
 	Day  = 24 * time.Hour
@@ -56,6 +56,8 @@ const (
 	MemoryBackend    = "memory"
 	MemoryBackend    = "memory"
 	SQLiteBackend    = "sqlite3"
 	SQLiteBackend    = "sqlite3"
 	RethinkDBBackend = "rethinkdb"
 	RethinkDBBackend = "rethinkdb"
+
+	DefaultImportRole = "delegation"
 )
 )
 
 
 // NotaryDefaultExpiries is the construct used to configure the default expiry times of
 // NotaryDefaultExpiries is the construct used to configure the default expiry times of

+ 16 - 0
vendor/src/github.com/docker/notary/const_nowindows.go

@@ -0,0 +1,16 @@
+// +build !windows
+
+package notary
+
+import (
+	"os"
+	"syscall"
+)
+
+// NotarySupportedSignals contains the signals we would like to capture:
+// - SIGUSR1, indicates a increment of the log level.
+// - SIGUSR2, indicates a decrement of the log level.
+var NotarySupportedSignals = []os.Signal{
+	syscall.SIGUSR1,
+	syscall.SIGUSR2,
+}

+ 8 - 0
vendor/src/github.com/docker/notary/const_windows.go

@@ -0,0 +1,8 @@
+// +build windows
+
+package notary
+
+import "os"
+
+// NotarySupportedSignals does not contain any signals, because SIGUSR1/2 are not supported on windows
+var NotarySupportedSignals = []os.Signal{}

+ 0 - 10
vendor/src/github.com/docker/notary/coverpkg.sh

@@ -1,10 +0,0 @@
-#!/usr/bin/env bash
-
-# Given a subpackage and the containing package, figures out which packages
-# need to be passed to `go test -coverpkg`:  this includes all of the
-# subpackage's dependencies within the containing package, as well as the
-# subpackage itself.
-
-DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2} | grep -v ${2}/vendor)"
-
-echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','

+ 2 - 2
vendor/src/github.com/docker/notary/cryptoservice/certificate.go

@@ -7,8 +7,8 @@ import (
 	"fmt"
 	"fmt"
 	"time"
 	"time"
 
 
-	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
 // GenerateCertificate generates an X509 Certificate from a template, given a GUN and validity interval
 // GenerateCertificate generates an X509 Certificate from a template, given a GUN and validity interval
@@ -22,7 +22,7 @@ func GenerateCertificate(rootKey data.PrivateKey, gun string, startTime, endTime
 }
 }
 
 
 func generateCertificate(signer crypto.Signer, gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
 func generateCertificate(signer crypto.Signer, gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
-	template, err := trustmanager.NewCertificate(gun, startTime, endTime)
+	template, err := utils.NewCertificate(gun, startTime, endTime)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
 		return nil, fmt.Errorf("failed to create the certificate template for: %s (%v)", gun, err)
 	}
 	}

+ 31 - 5
vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go

@@ -4,13 +4,24 @@ import (
 	"crypto/rand"
 	"crypto/rand"
 	"fmt"
 	"fmt"
 
 
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/notary"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
-const (
-	rsaKeySize = 2048 // Used for snapshots and targets keys
+var (
+	// ErrNoValidPrivateKey is returned if a key being imported doesn't
+	// look like a private key
+	ErrNoValidPrivateKey = errors.New("no valid private key found")
+
+	// ErrRootKeyNotEncrypted is returned if a root key being imported is
+	// unencrypted
+	ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
 )
 )
 
 
 // CryptoService implements Sign and Create, holding a specific GUN and keystore to
 // CryptoService implements Sign and Create, holding a specific GUN and keystore to
@@ -31,17 +42,17 @@ func (cs *CryptoService) Create(role, gun, algorithm string) (data.PublicKey, er
 
 
 	switch algorithm {
 	switch algorithm {
 	case data.RSAKey:
 	case data.RSAKey:
-		privKey, err = trustmanager.GenerateRSAKey(rand.Reader, rsaKeySize)
+		privKey, err = utils.GenerateRSAKey(rand.Reader, notary.MinRSABitSize)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("failed to generate RSA key: %v", err)
 			return nil, fmt.Errorf("failed to generate RSA key: %v", err)
 		}
 		}
 	case data.ECDSAKey:
 	case data.ECDSAKey:
-		privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
+		privKey, err = utils.GenerateECDSAKey(rand.Reader)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("failed to generate EC key: %v", err)
 			return nil, fmt.Errorf("failed to generate EC key: %v", err)
 		}
 		}
 	case data.ED25519Key:
 	case data.ED25519Key:
-		privKey, err = trustmanager.GenerateED25519Key(rand.Reader)
+		privKey, err = utils.GenerateED25519Key(rand.Reader)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("failed to generate ED25519 key: %v", err)
 			return nil, fmt.Errorf("failed to generate ED25519 key: %v", err)
 		}
 		}
@@ -153,3 +164,18 @@ func (cs *CryptoService) ListAllKeys() map[string]string {
 	}
 	}
 	return res
 	return res
 }
 }
+
+// CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
+// internal assumptions that depend on this.
+func CheckRootKeyIsEncrypted(pemBytes []byte) error {
+	block, _ := pem.Decode(pemBytes)
+	if block == nil {
+		return ErrNoValidPrivateKey
+	}
+
+	if !x509.IsEncryptedPEMBlock(block) {
+		return ErrRootKeyNotEncrypted
+	}
+
+	return nil
+}

+ 0 - 313
vendor/src/github.com/docker/notary/cryptoservice/import_export.go

@@ -1,313 +0,0 @@
-package cryptoservice
-
-import (
-	"archive/zip"
-	"crypto/x509"
-	"encoding/pem"
-	"errors"
-	"io"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/docker/notary/passphrase"
-	"github.com/docker/notary/trustmanager"
-)
-
-const zipMadeByUNIX = 3 << 8
-
-var (
-	// ErrNoValidPrivateKey is returned if a key being imported doesn't
-	// look like a private key
-	ErrNoValidPrivateKey = errors.New("no valid private key found")
-
-	// ErrRootKeyNotEncrypted is returned if a root key being imported is
-	// unencrypted
-	ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
-
-	// ErrNoKeysFoundForGUN is returned if no keys are found for the
-	// specified GUN during export
-	ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
-)
-
-// ExportKey exports the specified private key to an io.Writer in PEM format.
-// The key's existing encryption is preserved.
-func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
-	var (
-		pemBytes []byte
-		err      error
-	)
-
-	for _, ks := range cs.keyStores {
-		pemBytes, err = ks.ExportKey(keyID)
-		if err != nil {
-			continue
-		}
-	}
-	if err != nil {
-		return err
-	}
-
-	nBytes, err := dest.Write(pemBytes)
-	if err != nil {
-		return err
-	}
-	if nBytes != len(pemBytes) {
-		return errors.New("Unable to finish writing exported key.")
-	}
-	return nil
-}
-
-// ExportKeyReencrypt exports the specified private key to an io.Writer in
-// PEM format. The key is reencrypted with a new passphrase.
-func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
-	privateKey, _, err := cs.GetPrivateKey(keyID)
-	if err != nil {
-		return err
-	}
-
-	keyInfo, err := cs.GetKeyInfo(keyID)
-	if err != nil {
-		return err
-	}
-
-	// Create temporary keystore to use as a staging area
-	tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
-	defer os.RemoveAll(tempBaseDir)
-
-	tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever)
-	if err != nil {
-		return err
-	}
-
-	err = tempKeyStore.AddKey(keyInfo, privateKey)
-	if err != nil {
-		return err
-	}
-
-	pemBytes, err := tempKeyStore.ExportKey(keyID)
-	if err != nil {
-		return err
-	}
-	nBytes, err := dest.Write(pemBytes)
-	if err != nil {
-		return err
-	}
-	if nBytes != len(pemBytes) {
-		return errors.New("Unable to finish writing exported key.")
-	}
-	return nil
-}
-
-// ExportAllKeys exports all keys to an io.Writer in zip format.
-// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
-func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
-	tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
-	defer os.RemoveAll(tempBaseDir)
-
-	// Create temporary keystore to use as a staging area
-	tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever)
-	if err != nil {
-		return err
-	}
-
-	for _, ks := range cs.keyStores {
-		if err := moveKeys(ks, tempKeyStore); err != nil {
-			return err
-		}
-	}
-
-	zipWriter := zip.NewWriter(dest)
-
-	if err := addKeysToArchive(zipWriter, tempKeyStore); err != nil {
-		return err
-	}
-
-	zipWriter.Close()
-
-	return nil
-}
-
-// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
-// keys in the root_keys directory are left encrypted, but the other keys are
-// decrypted with the specified passphrase.
-func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader, retriever passphrase.Retriever) error {
-	// Temporarily store the keys in maps, so we can bail early if there's
-	// an error (for example, wrong passphrase), without leaving the key
-	// store in an inconsistent state
-	newKeys := make(map[string][]byte)
-
-	// Iterate through the files in the archive. Don't add the keys
-	for _, f := range zipReader.File {
-		fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
-		rc, err := f.Open()
-		if err != nil {
-			return err
-		}
-		defer rc.Close()
-
-		fileBytes, err := ioutil.ReadAll(rc)
-		if err != nil {
-			return nil
-		}
-
-		// Note that using / as a separator is okay here - the zip
-		// package guarantees that the separator will be /
-		if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
-			if err = CheckRootKeyIsEncrypted(fileBytes); err != nil {
-				return err
-			}
-		}
-		newKeys[fNameTrimmed] = fileBytes
-	}
-
-	for keyName, pemBytes := range newKeys {
-		// Get the key role information as well as its data.PrivateKey representation
-		_, keyInfo, err := trustmanager.KeyInfoFromPEM(pemBytes, keyName)
-		if err != nil {
-			return err
-		}
-		privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
-		if err != nil {
-			privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", "imported "+keyInfo.Role)
-			if err != nil {
-				return err
-			}
-		}
-		// Add the key to our cryptoservice, will add to the first successful keystore
-		if err = cs.AddKey(keyInfo.Role, keyInfo.Gun, privKey); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// ExportKeysByGUN exports all keys associated with a specified GUN to an
-// io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to
-// encrypt the keys.
-func (cs *CryptoService) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever passphrase.Retriever) error {
-	tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
-	defer os.RemoveAll(tempBaseDir)
-
-	// Create temporary keystore to use as a staging area
-	tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever)
-	if err != nil {
-		return err
-	}
-
-	for _, ks := range cs.keyStores {
-		if err := moveKeysByGUN(ks, tempKeyStore, gun); err != nil {
-			return err
-		}
-	}
-
-	zipWriter := zip.NewWriter(dest)
-
-	if len(tempKeyStore.ListKeys()) == 0 {
-		return ErrNoKeysFoundForGUN
-	}
-
-	if err := addKeysToArchive(zipWriter, tempKeyStore); err != nil {
-		return err
-	}
-
-	zipWriter.Close()
-
-	return nil
-}
-
-func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error {
-	for keyID, keyInfo := range oldKeyStore.ListKeys() {
-		// Skip keys that aren't associated with this GUN
-		if keyInfo.Gun != gun {
-			continue
-		}
-
-		privKey, _, err := oldKeyStore.GetKey(keyID)
-		if err != nil {
-			return err
-		}
-
-		err = newKeyStore.AddKey(keyInfo, privKey)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error {
-	for keyID, keyInfo := range oldKeyStore.ListKeys() {
-		privateKey, _, err := oldKeyStore.GetKey(keyID)
-		if err != nil {
-			return err
-		}
-
-		err = newKeyStore.AddKey(keyInfo, privateKey)
-
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileStore) error {
-	for _, relKeyPath := range newKeyStore.ListFiles() {
-		fullKeyPath, err := newKeyStore.GetPath(relKeyPath)
-		if err != nil {
-			return err
-		}
-
-		fi, err := os.Lstat(fullKeyPath)
-		if err != nil {
-			return err
-		}
-
-		infoHeader, err := zip.FileInfoHeader(fi)
-		if err != nil {
-			return err
-		}
-
-		relPath, err := filepath.Rel(newKeyStore.BaseDir(), fullKeyPath)
-		if err != nil {
-			return err
-		}
-		infoHeader.Name = relPath
-
-		zipFileEntryWriter, err := zipWriter.CreateHeader(infoHeader)
-		if err != nil {
-			return err
-		}
-
-		fileContents, err := ioutil.ReadFile(fullKeyPath)
-		if err != nil {
-			return err
-		}
-
-		if _, err = zipFileEntryWriter.Write(fileContents); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
-// internal assumptions that depend on this.
-func CheckRootKeyIsEncrypted(pemBytes []byte) error {
-	block, _ := pem.Decode(pemBytes)
-	if block == nil {
-		return ErrNoValidPrivateKey
-	}
-
-	if !x509.IsEncryptedPEMBlock(block) {
-		return ErrRootKeyNotEncrypted
-	}
-
-	return nil
-}

+ 60 - 0
vendor/src/github.com/docker/notary/development.mysql.yml

@@ -0,0 +1,60 @@
+version: "2"
+services:
+  server:
+    build:
+      context: .
+      dockerfile: server.Dockerfile
+    networks:
+      mdb:
+      sig:
+      srv:
+        aliases:
+          - notary-server
+    entrypoint: /usr/bin/env sh
+    command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
+    depends_on:
+      - mysql
+      - signer
+  signer:
+    build:
+      context: .
+      dockerfile: signer.Dockerfile
+    networks:
+      mdb:
+      sig:
+        aliases:
+          - notarysigner
+    entrypoint: /usr/bin/env sh
+    command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
+    depends_on:
+      - mysql
+  mysql:
+    networks:
+      - mdb
+    volumes:
+      - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
+    image: mariadb:10.1.10
+    environment:
+      - TERM=dumb
+      - MYSQL_ALLOW_EMPTY_PASSWORD="true"
+    command: mysqld --innodb_file_per_table
+  client:
+    build:
+      context: .
+      dockerfile: Dockerfile
+    env_file: buildscripts/env.list
+    command: buildscripts/testclient.py
+    volumes:
+      - ./test_output:/test_output
+    networks:
+      - mdb
+      - srv
+    depends_on:
+      - server
+networks:
+  mdb:
+    external: false
+  sig:
+    external: false
+  srv:
+    external: false

+ 10 - 13
vendor/src/github.com/docker/notary/development.rethink.yml

@@ -11,8 +11,6 @@ services:
       links:
       links:
         - rdb-proxy:rdb-proxy.rdb
         - rdb-proxy:rdb-proxy.rdb
         - signer
         - signer
-      environment:
-        - SERVICE_NAME=notary_server
       ports:
       ports:
         - "8080"
         - "8080"
         - "4443:4443"
         - "4443:4443"
@@ -32,14 +30,12 @@ services:
                 - notarysigner
                 - notarysigner
       links:
       links:
         - rdb-proxy:rdb-proxy.rdb
         - rdb-proxy:rdb-proxy.rdb
-      environment:
-        - SERVICE_NAME=notary_signer
       entrypoint: /usr/bin/env sh
       entrypoint: /usr/bin/env sh
       command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
       command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
       depends_on:
       depends_on:
         - rdb-proxy
         - rdb-proxy
     rdb-01:
     rdb-01:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-01-data:/var/data
         - rdb-01-data:/var/data
@@ -51,7 +47,7 @@ services:
             - rdb-01.rdb
             - rdb-01.rdb
       command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
       command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
     rdb-02:
     rdb-02:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-02-data:/var/data
         - rdb-02-data:/var/data
@@ -63,7 +59,7 @@ services:
             - rdb-02.rdb
             - rdb-02.rdb
       command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
       command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
     rdb-03:
     rdb-03:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-03-data:/var/data
         - rdb-03-data:/var/data
@@ -75,7 +71,7 @@ services:
             - rdb-03.rdb
             - rdb-03.rdb
       command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
       command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
     rdb-proxy:
     rdb-proxy:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       ports:
       ports:
         - "8080:8080"
         - "8080:8080"
       volumes:
       volumes:
@@ -91,16 +87,17 @@ services:
         - rdb-02
         - rdb-02
         - rdb-03
         - rdb-03
     client:
     client:
+      build:
+        context: .
+        dockerfile: Dockerfile
       volumes:
       volumes:
         - ./test_output:/test_output
         - ./test_output:/test_output
       networks:
       networks:
         - rdb
         - rdb
-      build:
-        context: .
-        dockerfile: Dockerfile
+      env_file: buildscripts/env.list
       links:
       links:
         - server:notary-server
         - server:notary-server
-      command: buildscripts/testclient.sh
+      command: buildscripts/testclient.py
 volumes:
 volumes:
     rdb-01-data:
     rdb-01-data:
         external: false
         external: false
@@ -110,4 +107,4 @@ volumes:
         external: false
         external: false
 networks:
 networks:
     rdb:
     rdb:
-        external: false
+        external: false

+ 0 - 36
vendor/src/github.com/docker/notary/development.yml

@@ -1,36 +0,0 @@
-server:
-  build: .
-  dockerfile: server.Dockerfile
-  links:
-    - mysql
-    - signer
-    - signer:notarysigner
-  environment:
-    - SERVICE_NAME=notary_server
-  entrypoint: /usr/bin/env sh
-  command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
-signer:
-  build: .
-  dockerfile: signer.Dockerfile
-  links:
-    - mysql
-  environment:
-    - SERVICE_NAME=notary_signer
-  entrypoint: /usr/bin/env sh
-  command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
-mysql:
-  volumes:
-    - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
-  image: mariadb:10.1.10
-  environment:
-    - TERM=dumb
-    - MYSQL_ALLOW_EMPTY_PASSWORD="true"
-  command: mysqld --innodb_file_per_table
-client:
-  volumes:
-    - ./test_output:/test_output
-  build: .
-  dockerfile: Dockerfile
-  links:
-    - server:notary-server
-  command: buildscripts/testclient.sh

+ 15 - 21
vendor/src/github.com/docker/notary/docker-compose.rethink.yml

@@ -1,7 +1,7 @@
 version: "2"
 version: "2"
 services:
 services:
     server:
     server:
-      build: 
+      build:
         context: .
         context: .
         dockerfile: server.Dockerfile
         dockerfile: server.Dockerfile
       volumes:
       volumes:
@@ -11,17 +11,14 @@ services:
       links:
       links:
         - rdb-proxy:rdb-proxy.rdb
         - rdb-proxy:rdb-proxy.rdb
         - signer
         - signer
-      environment:
-        - SERVICE_NAME=notary_server
       ports:
       ports:
-        - "8080"
         - "4443:4443"
         - "4443:4443"
       entrypoint: /usr/bin/env sh
       entrypoint: /usr/bin/env sh
       command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
       command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
       depends_on:
       depends_on:
         - rdb-proxy
         - rdb-proxy
     signer:
     signer:
-      build: 
+      build:
         context: .
         context: .
         dockerfile: signer.Dockerfile
         dockerfile: signer.Dockerfile
       volumes:
       volumes:
@@ -32,50 +29,47 @@ services:
                 - notarysigner
                 - notarysigner
       links:
       links:
         - rdb-proxy:rdb-proxy.rdb
         - rdb-proxy:rdb-proxy.rdb
-      environment:
-        - SERVICE_NAME=notary_signer
       entrypoint: /usr/bin/env sh
       entrypoint: /usr/bin/env sh
       command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
       command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
       depends_on:
       depends_on:
         - rdb-proxy
         - rdb-proxy
     rdb-01:
     rdb-01:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-01-data:/var/data
         - rdb-01-data:/var/data
       networks:
       networks:
         rdb:
         rdb:
           aliases:
           aliases:
-            - rdb
-            - rdb.rdb
             - rdb-01.rdb
             - rdb-01.rdb
-      command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
     rdb-02:
     rdb-02:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-02-data:/var/data
         - rdb-02-data:/var/data
       networks:
       networks:
         rdb:
         rdb:
           aliases:
           aliases:
-            - rdb
-            - rdb.rdb
             - rdb-02.rdb
             - rdb-02.rdb
-      command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb-01 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      depends_on:
+        - rdb-01
     rdb-03:
     rdb-03:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       volumes:
       volumes:
         - ./fixtures/rethinkdb:/tls
         - ./fixtures/rethinkdb:/tls
         - rdb-03-data:/var/data
         - rdb-03-data:/var/data
       networks:
       networks:
         rdb:
         rdb:
           aliases:
           aliases:
-            - rdb
-            - rdb.rdb
             - rdb-03.rdb
             - rdb-03.rdb
-      command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb-02 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      depends_on:
+        - rdb-01
+        - rdb-02
     rdb-proxy:
     rdb-proxy:
-      image: jlhawn/rethinkdb:2.3.0
+      image: jlhawn/rethinkdb:2.3.4
       ports:
       ports:
         - "8080:8080"
         - "8080:8080"
       volumes:
       volumes:
@@ -85,7 +79,7 @@ services:
           aliases:
           aliases:
             - rdb-proxy
             - rdb-proxy
             - rdb-proxy.rdp
             - rdb-proxy.rdp
-      command: "proxy --bind all --join rdb.rdb --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
+      command: "proxy --bind all --join rdb-03 --driver-tls-ca /tls/ca.pem --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
       depends_on:
       depends_on:
         - rdb-01
         - rdb-01
         - rdb-02
         - rdb-02

+ 49 - 34
vendor/src/github.com/docker/notary/docker-compose.yml

@@ -1,34 +1,49 @@
-server:
-  build: .
-  dockerfile: server.Dockerfile
-  links:
-    - mysql
-    - signer
-    - signer:notarysigner
-  environment:
-    - SERVICE_NAME=notary_server
-  ports:
-    - "8080"
-    - "4443:4443"
-  entrypoint: /usr/bin/env sh
-  command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
-signer:
-  build: .
-  dockerfile: signer.Dockerfile
-  links:
-    - mysql
-  environment:
-    - SERVICE_NAME=notary_signer
-  entrypoint: /usr/bin/env sh
-  command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
-mysql:
-  volumes:
-    - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
-    - notary_data:/var/lib/mysql
-  image: mariadb:10.1.10
-  ports:
-    - "3306:3306"
-  environment:
-    - TERM=dumb
-    - MYSQL_ALLOW_EMPTY_PASSWORD="true"
-  command: mysqld --innodb_file_per_table
+version: "2"
+services:
+  server:
+    build:
+      context: .
+      dockerfile: server.Dockerfile
+    networks:
+      - mdb
+      - sig
+    ports:
+      - "8080"
+      - "4443:4443"
+    entrypoint: /usr/bin/env sh
+    command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
+    depends_on:
+      - mysql
+      - signer
+  signer:
+    build:
+      context: .
+      dockerfile: signer.Dockerfile
+    networks:
+      mdb:
+      sig:
+        aliases:
+          - notarysigner
+    entrypoint: /usr/bin/env sh
+    command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
+    depends_on:
+      - mysql
+  mysql:
+    networks:
+      - mdb
+    volumes:
+      - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
+      - notary_data:/var/lib/mysql
+    image: mariadb:10.1.10
+    environment:
+      - TERM=dumb
+      - MYSQL_ALLOW_EMPTY_PASSWORD="true"
+    command: mysqld --innodb_file_per_table
+volumes:
+  notary_data:
+    external: false
+networks:
+  mdb:
+    external: false
+  sig:
+    external: false

+ 7 - 0
vendor/src/github.com/docker/notary/notary.go

@@ -0,0 +1,7 @@
+package notary
+
+// PassRetriever is a callback function that should retrieve a passphrase
+// for a given named key. If it should be treated as new passphrase (e.g. with
+// confirmation), createNew will be true. Attempts is passed in so that implementers
+// decide how many chances to give to a human, for example.
+type PassRetriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)

+ 125 - 121
vendor/src/github.com/docker/notary/passphrase/passphrase.go

@@ -8,19 +8,13 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"os"
 	"os"
-	"strings"
-
 	"path/filepath"
 	"path/filepath"
+	"strings"
 
 
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
+	"github.com/docker/notary"
 )
 )
 
 
-// Retriever is a callback function that should retrieve a passphrase
-// for a given named key. If it should be treated as new passphrase (e.g. with
-// confirmation), createNew will be true. Attempts is passed in so that implementers
-// decide how many chances to give to a human, for example.
-type Retriever func(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error)
-
 const (
 const (
 	idBytesToDisplay            = 7
 	idBytesToDisplay            = 7
 	tufRootAlias                = "root"
 	tufRootAlias                = "root"
@@ -46,155 +40,165 @@ var (
 	// ErrTooManyAttempts is returned if the maximum number of passphrase
 	// ErrTooManyAttempts is returned if the maximum number of passphrase
 	// entry attempts is reached.
 	// entry attempts is reached.
 	ErrTooManyAttempts = errors.New("Too many attempts")
 	ErrTooManyAttempts = errors.New("Too many attempts")
+
+	// ErrNoInput is returned if we do not have a valid input method for passphrases
+	ErrNoInput = errors.New("Please either use environment variables or STDIN with a terminal to provide key passphrases")
 )
 )
 
 
 // PromptRetriever returns a new Retriever which will provide a prompt on stdin
 // PromptRetriever returns a new Retriever which will provide a prompt on stdin
-// and stdout to retrieve a passphrase. The passphrase will be cached such that
+// and stdout to retrieve a passphrase. stdin will be checked if it is a terminal,
+// else the PromptRetriever will error when attempting to retrieve a passphrase.
+// Upon successful passphrase retrievals, the passphrase will be cached such that
 // subsequent prompts will produce the same passphrase.
 // subsequent prompts will produce the same passphrase.
-func PromptRetriever() Retriever {
+func PromptRetriever() notary.PassRetriever {
+	if !term.IsTerminal(os.Stdin.Fd()) {
+		return func(string, string, bool, int) (string, bool, error) {
+			return "", false, ErrNoInput
+		}
+	}
 	return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil)
 	return PromptRetrieverWithInOut(os.Stdin, os.Stdout, nil)
 }
 }
 
 
-// PromptRetrieverWithInOut returns a new Retriever which will provide a
-// prompt using the given in and out readers. The passphrase will be cached
-// such that subsequent prompts will produce the same passphrase.
-// aliasMap can be used to specify display names for TUF key aliases. If aliasMap
-// is nil, a sensible default will be used.
-func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) Retriever {
-	userEnteredTargetsSnapshotsPass := false
-	targetsSnapshotsPass := ""
-	userEnteredRootsPass := false
-	rootsPass := ""
-
-	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
-		if alias == tufRootAlias && createNew && numAttempts == 0 {
-			fmt.Fprintln(out, tufRootKeyGenerationWarning)
-		}
-		if numAttempts > 0 {
-			if !createNew {
-				fmt.Fprintln(out, "Passphrase incorrect. Please retry.")
-			}
-		}
-
-		// Figure out if we should display a different string for this alias
-		displayAlias := alias
-		if aliasMap != nil {
-			if val, ok := aliasMap[alias]; ok {
-				displayAlias = val
-			}
+type boundRetriever struct {
+	in              io.Reader
+	out             io.Writer
+	aliasMap        map[string]string
+	passphraseCache map[string]string
+}
 
 
+func (br *boundRetriever) getPassphrase(keyName, alias string, createNew bool, numAttempts int) (string, bool, error) {
+	if numAttempts == 0 {
+		if alias == tufRootAlias && createNew {
+			fmt.Fprintln(br.out, tufRootKeyGenerationWarning)
 		}
 		}
 
 
-		// First, check if we have a password cached for this alias.
-		if numAttempts == 0 {
-			if userEnteredTargetsSnapshotsPass && (alias == tufSnapshotAlias || alias == tufTargetsAlias) {
-				return targetsSnapshotsPass, false, nil
-			}
-			if userEnteredRootsPass && (alias == "root") {
-				return rootsPass, false, nil
-			}
+		if pass, ok := br.passphraseCache[alias]; ok {
+			return pass, false, nil
 		}
 		}
-
-		if numAttempts > 3 && !createNew {
+	} else if !createNew { // per `if`, numAttempts > 0 if we're at this `else`
+		if numAttempts > 3 {
 			return "", true, ErrTooManyAttempts
 			return "", true, ErrTooManyAttempts
 		}
 		}
+		fmt.Fprintln(br.out, "Passphrase incorrect. Please retry.")
+	}
 
 
-		// If typing on the terminal, we do not want the terminal to echo the
-		// password that is typed (so it doesn't display)
-		if term.IsTerminal(0) {
-			state, err := term.SaveState(0)
-			if err != nil {
-				return "", false, err
-			}
-			term.DisableEcho(0, state)
-			defer term.RestoreTerminal(0, state)
-		}
-
-		stdin := bufio.NewReader(in)
+	// passphrase not cached and we're not aborting, get passphrase from user!
+	return br.requestPassphrase(keyName, alias, createNew, numAttempts)
+}
 
 
-		indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator))
-		if indexOfLastSeparator == -1 {
-			indexOfLastSeparator = 0
-		}
+func (br *boundRetriever) requestPassphrase(keyName, alias string, createNew bool, numAttempts int) (string, bool, error) {
+	// Figure out if we should display a different string for this alias
+	displayAlias := alias
+	if val, ok := br.aliasMap[alias]; ok {
+		displayAlias = val
+	}
 
 
-		var shortName string
-		if len(keyName) > indexOfLastSeparator+idBytesToDisplay {
-			if indexOfLastSeparator > 0 {
-				keyNamePrefix := keyName[:indexOfLastSeparator]
-				keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1]
-				shortName = keyNameID + " (" + keyNamePrefix + ")"
-			} else {
-				shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay]
-			}
+	// If typing on the terminal, we do not want the terminal to echo the
+	// password that is typed (so it doesn't display)
+	if term.IsTerminal(os.Stdin.Fd()) {
+		state, err := term.SaveState(os.Stdin.Fd())
+		if err != nil {
+			return "", false, err
 		}
 		}
+		term.DisableEcho(os.Stdin.Fd(), state)
+		defer term.RestoreTerminal(os.Stdin.Fd(), state)
+	}
 
 
-		withID := fmt.Sprintf(" with ID %s", shortName)
-		if shortName == "" {
-			withID = ""
-		}
+	indexOfLastSeparator := strings.LastIndex(keyName, string(filepath.Separator))
+	if indexOfLastSeparator == -1 {
+		indexOfLastSeparator = 0
+	}
 
 
-		if createNew {
-			fmt.Fprintf(out, "Enter passphrase for new %s key%s: ", displayAlias, withID)
-		} else if displayAlias == "yubikey" {
-			fmt.Fprintf(out, "Enter the %s for the attached Yubikey: ", keyName)
+	var shortName string
+	if len(keyName) > indexOfLastSeparator+idBytesToDisplay {
+		if indexOfLastSeparator > 0 {
+			keyNamePrefix := keyName[:indexOfLastSeparator]
+			keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1]
+			shortName = keyNameID + " (" + keyNamePrefix + ")"
 		} else {
 		} else {
-			fmt.Fprintf(out, "Enter passphrase for %s key%s: ", displayAlias, withID)
+			shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay]
 		}
 		}
+	}
 
 
-		passphrase, err := stdin.ReadBytes('\n')
-		fmt.Fprintln(out)
-		if err != nil {
-			return "", false, err
-		}
+	withID := fmt.Sprintf(" with ID %s", shortName)
+	if shortName == "" {
+		withID = ""
+	}
 
 
-		retPass := strings.TrimSpace(string(passphrase))
-
-		if !createNew {
-			if alias == tufSnapshotAlias || alias == tufTargetsAlias {
-				userEnteredTargetsSnapshotsPass = true
-				targetsSnapshotsPass = retPass
-			}
-			if alias == tufRootAlias {
-				userEnteredRootsPass = true
-				rootsPass = retPass
-			}
-			return retPass, false, nil
-		}
+	switch {
+	case createNew:
+		fmt.Fprintf(br.out, "Enter passphrase for new %s key%s: ", displayAlias, withID)
+	case displayAlias == "yubikey":
+		fmt.Fprintf(br.out, "Enter the %s for the attached Yubikey: ", keyName)
+	default:
+		fmt.Fprintf(br.out, "Enter passphrase for %s key%s: ", displayAlias, withID)
+	}
 
 
-		if len(retPass) < 8 {
-			fmt.Fprintln(out, "Passphrase is too short. Please use a password manager to generate and store a good random passphrase.")
-			return "", false, ErrTooShort
-		}
+	stdin := bufio.NewReader(br.in)
+	passphrase, err := stdin.ReadBytes('\n')
+	fmt.Fprintln(br.out)
+	if err != nil {
+		return "", false, err
+	}
 
 
-		fmt.Fprintf(out, "Repeat passphrase for new %s key%s: ", displayAlias, withID)
-		confirmation, err := stdin.ReadBytes('\n')
-		fmt.Fprintln(out)
+	retPass := strings.TrimSpace(string(passphrase))
+
+	if createNew {
+		err = br.verifyAndConfirmPassword(stdin, retPass, displayAlias, withID)
 		if err != nil {
 		if err != nil {
 			return "", false, err
 			return "", false, err
 		}
 		}
-		confirmationStr := strings.TrimSpace(string(confirmation))
+	}
 
 
-		if retPass != confirmationStr {
-			fmt.Fprintln(out, "Passphrases do not match. Please retry.")
-			return "", false, ErrDontMatch
-		}
+	br.cachePassword(alias, retPass)
 
 
-		if alias == tufSnapshotAlias || alias == tufTargetsAlias {
-			userEnteredTargetsSnapshotsPass = true
-			targetsSnapshotsPass = retPass
-		}
-		if alias == tufRootAlias {
-			userEnteredRootsPass = true
-			rootsPass = retPass
-		}
+	return retPass, false, nil
+}
 
 
-		return retPass, false, nil
+func (br *boundRetriever) verifyAndConfirmPassword(stdin *bufio.Reader, retPass, displayAlias, withID string) error {
+	if len(retPass) < 8 {
+		fmt.Fprintln(br.out, "Passphrase is too short. Please use a password manager to generate and store a good random passphrase.")
+		return ErrTooShort
 	}
 	}
+
+	fmt.Fprintf(br.out, "Repeat passphrase for new %s key%s: ", displayAlias, withID)
+	confirmation, err := stdin.ReadBytes('\n')
+	fmt.Fprintln(br.out)
+	if err != nil {
+		return err
+	}
+	confirmationStr := strings.TrimSpace(string(confirmation))
+
+	if retPass != confirmationStr {
+		fmt.Fprintln(br.out, "Passphrases do not match. Please retry.")
+		return ErrDontMatch
+	}
+	return nil
+}
+
+func (br *boundRetriever) cachePassword(alias, retPass string) {
+	br.passphraseCache[alias] = retPass
+}
+
+// PromptRetrieverWithInOut returns a new Retriever which will provide a
+// prompt using the given in and out readers. The passphrase will be cached
+// such that subsequent prompts will produce the same passphrase.
+// aliasMap can be used to specify display names for TUF key aliases. If aliasMap
+// is nil, a sensible default will be used.
+func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]string) notary.PassRetriever {
+	bound := &boundRetriever{
+		in:              in,
+		out:             out,
+		aliasMap:        aliasMap,
+		passphraseCache: make(map[string]string),
+	}
+
+	return bound.getPassphrase
 }
 }
 
 
 // ConstantRetriever returns a new Retriever which will return a constant string
 // ConstantRetriever returns a new Retriever which will return a constant string
 // as a passphrase.
 // as a passphrase.
-func ConstantRetriever(constantPassphrase string) Retriever {
+func ConstantRetriever(constantPassphrase string) notary.PassRetriever {
 	return func(k, a string, c bool, n int) (string, bool, error) {
 	return func(k, a string, c bool, n int) (string, bool, error) {
 		return constantPassphrase, false, nil
 		return constantPassphrase, false, nil
 	}
 	}

+ 2 - 1
vendor/src/github.com/docker/notary/server.Dockerfile

@@ -1,4 +1,4 @@
-FROM golang:1.6.1-alpine
+FROM golang:1.7.1-alpine
 MAINTAINER David Lawrence "david.lawrence@docker.com"
 MAINTAINER David Lawrence "david.lawrence@docker.com"
 
 
 RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
 RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
@@ -13,6 +13,7 @@ COPY . /go/src/${NOTARYPKG}
 
 
 WORKDIR /go/src/${NOTARYPKG}
 WORKDIR /go/src/${NOTARYPKG}
 
 
+ENV SERVICE_NAME=notary_server
 EXPOSE 4443
 EXPOSE 4443
 
 
 # Install notary-server
 # Install notary-server

+ 2 - 3
vendor/src/github.com/docker/notary/signer.Dockerfile

@@ -1,4 +1,4 @@
-FROM golang:1.6.1-alpine
+FROM golang:1.7.1-alpine
 MAINTAINER David Lawrence "david.lawrence@docker.com"
 MAINTAINER David Lawrence "david.lawrence@docker.com"
 
 
 RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
 RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
@@ -13,11 +13,10 @@ COPY . /go/src/${NOTARYPKG}
 
 
 WORKDIR /go/src/${NOTARYPKG}
 WORKDIR /go/src/${NOTARYPKG}
 
 
+ENV SERVICE_NAME=notary_signer
 ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
 ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
 ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
 ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
 
 
-EXPOSE 4444
-
 # Install notary-signer
 # Install notary-signer
 RUN go install \
 RUN go install \
     -tags pkcs11 \
     -tags pkcs11 \

+ 11 - 2
vendor/src/github.com/docker/notary/tuf/store/errors.go → vendor/src/github.com/docker/notary/storage/errors.go

@@ -1,6 +1,15 @@
-package store
+package storage
 
 
-import "fmt"
+import (
+	"errors"
+	"fmt"
+)
+
+var (
+	// ErrPathOutsideStore indicates that the returned path would be
+	// outside the store
+	ErrPathOutsideStore = errors.New("path outside file store")
+)
 
 
 // ErrMetaNotFound indicates we did not find a particular piece
 // ErrMetaNotFound indicates we did not find a particular piece
 // of metadata in the store
 // of metadata in the store

+ 222 - 0
vendor/src/github.com/docker/notary/storage/filestore.go

@@ -0,0 +1,222 @@
+package storage
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/docker/notary"
+)
+
+// NewFilesystemStore creates a new store in a directory tree
+func NewFilesystemStore(baseDir, subDir, extension string) (*FilesystemStore, error) {
+	baseDir = filepath.Join(baseDir, subDir)
+
+	return NewFileStore(baseDir, extension, notary.PrivKeyPerms)
+}
+
+// NewFileStore creates a fully configurable file store
+func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*FilesystemStore, error) {
+	baseDir = filepath.Clean(baseDir)
+	if err := createDirectory(baseDir, perms); err != nil {
+		return nil, err
+	}
+	if !strings.HasPrefix(fileExt, ".") {
+		fileExt = "." + fileExt
+	}
+
+	return &FilesystemStore{
+		baseDir: baseDir,
+		ext:     fileExt,
+		perms:   perms,
+	}, nil
+}
+
+// NewSimpleFileStore is a convenience wrapper to create a world readable,
+// owner writeable filestore
+func NewSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
+	return NewFileStore(baseDir, fileExt, notary.PubCertPerms)
+}
+
+// NewPrivateKeyFileStorage initializes a new filestore for private keys, appending
+// the notary.PrivDir to the baseDir.
+func NewPrivateKeyFileStorage(baseDir, fileExt string) (*FilesystemStore, error) {
+	baseDir = filepath.Join(baseDir, notary.PrivDir)
+	return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms)
+}
+
+// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
+// _only_ filestore
+func NewPrivateSimpleFileStore(baseDir, fileExt string) (*FilesystemStore, error) {
+	return NewFileStore(baseDir, fileExt, notary.PrivKeyPerms)
+}
+
+// FilesystemStore is a store in a locally accessible directory
+type FilesystemStore struct {
+	baseDir string
+	ext     string
+	perms   os.FileMode
+}
+
+func (f *FilesystemStore) getPath(name string) (string, error) {
+	fileName := fmt.Sprintf("%s%s", name, f.ext)
+	fullPath := filepath.Join(f.baseDir, fileName)
+
+	if !strings.HasPrefix(fullPath, f.baseDir) {
+		return "", ErrPathOutsideStore
+	}
+	return fullPath, nil
+}
+
+// GetSized returns the meta for the given name (a role) up to size bytes
+// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
+// predefined threshold "notary.MaxDownloadSize". If the file is larger than size
+// we return ErrMaliciousServer for consistency with the HTTPStore
+func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) {
+	p, err := f.getPath(name)
+	if err != nil {
+		return nil, err
+	}
+	file, err := os.OpenFile(p, os.O_RDONLY, f.perms)
+	if err != nil {
+		if os.IsNotExist(err) {
+			err = ErrMetaNotFound{Resource: name}
+		}
+		return nil, err
+	}
+	defer file.Close()
+
+	if size == NoSizeLimit {
+		size = notary.MaxDownloadSize
+	}
+
+	stat, err := file.Stat()
+	if err != nil {
+		return nil, err
+	}
+	if stat.Size() > size {
+		return nil, ErrMaliciousServer{}
+	}
+
+	l := io.LimitReader(file, size)
+	return ioutil.ReadAll(l)
+}
+
+// Get returns the meta for the given name.
+func (f *FilesystemStore) Get(name string) ([]byte, error) {
+	p, err := f.getPath(name)
+	if err != nil {
+		return nil, err
+	}
+	meta, err := ioutil.ReadFile(p)
+	if err != nil {
+		if os.IsNotExist(err) {
+			err = ErrMetaNotFound{Resource: name}
+		}
+		return nil, err
+	}
+	return meta, nil
+}
+
+// SetMulti sets the metadata for multiple roles in one operation
+func (f *FilesystemStore) SetMulti(metas map[string][]byte) error {
+	for role, blob := range metas {
+		err := f.Set(role, blob)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Set sets the meta for a single role
+func (f *FilesystemStore) Set(name string, meta []byte) error {
+	fp, err := f.getPath(name)
+	if err != nil {
+		return err
+	}
+
+	// Ensures the parent directories of the file we are about to write exist
+	err = os.MkdirAll(filepath.Dir(fp), f.perms)
+	if err != nil {
+		return err
+	}
+
+	// if something already exists, just delete it and re-write it
+	os.RemoveAll(fp)
+
+	// Write the file to disk
+	if err = ioutil.WriteFile(fp, meta, f.perms); err != nil {
+		return err
+	}
+	return nil
+}
+
+// RemoveAll clears the existing filestore by removing its base directory
+func (f *FilesystemStore) RemoveAll() error {
+	return os.RemoveAll(f.baseDir)
+}
+
+// Remove removes the metadata for a single role - if the metadata doesn't
+// exist, no error is returned
+func (f *FilesystemStore) Remove(name string) error {
+	p, err := f.getPath(name)
+	if err != nil {
+		return err
+	}
+	return os.RemoveAll(p) // RemoveAll succeeds if path doesn't exist
+}
+
+// Location returns a human readable name for the storage location
+func (f FilesystemStore) Location() string {
+	return f.baseDir
+}
+
+// ListFiles returns a list of all the filenames that can be used with Get*
+// to retrieve content from this filestore
+func (f FilesystemStore) ListFiles() []string {
+	files := make([]string, 0, 0)
+	filepath.Walk(f.baseDir, func(fp string, fi os.FileInfo, err error) error {
+		// If there are errors, ignore this particular file
+		if err != nil {
+			return nil
+		}
+		// Ignore if it is a directory
+		if fi.IsDir() {
+			return nil
+		}
+
+		// If this is a symlink, ignore it
+		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+			return nil
+		}
+
+		// Only allow matches that end with our certificate extension (e.g. *.crt)
+		matched, _ := filepath.Match("*"+f.ext, fi.Name())
+
+		if matched {
+			// Find the relative path for this file relative to the base path.
+			fp, err = filepath.Rel(f.baseDir, fp)
+			if err != nil {
+				return err
+			}
+			trimmed := strings.TrimSuffix(fp, f.ext)
+			files = append(files, trimmed)
+		}
+		return nil
+	})
+	return files
+}
+
+// createDirectory receives a string of the path to a directory.
+// It does not support passing files, so the caller has to remove
+// the filename by doing filepath.Dir(full_path_to_file)
+func createDirectory(dir string, perms os.FileMode) error {
+	// This prevents someone passing /path/to/dir and 'dir' not being created
+	// If two '//' exist, MkdirAll deals it with correctly
+	dir = dir + "/"
+	return os.MkdirAll(dir, perms)
+}

+ 70 - 28
vendor/src/github.com/docker/notary/tuf/store/httpstore.go → vendor/src/github.com/docker/notary/storage/httpstore.go

@@ -8,7 +8,7 @@
 //   If writing your own server, please have a look at
 //   If writing your own server, please have a look at
 //   github.com/docker/distribution/registry/api/errcode
 //   github.com/docker/distribution/registry/api/errcode
 
 
-package store
+package storage
 
 
 import (
 import (
 	"bytes"
 	"bytes"
@@ -33,6 +33,15 @@ type ErrServerUnavailable struct {
 	code int
 	code int
 }
 }
 
 
+// NetworkError represents any kind of network error when attempting to make a request
+type NetworkError struct {
+	Wrapped error
+}
+
+func (n NetworkError) Error() string {
+	return n.Wrapped.Error()
+}
+
 func (err ErrServerUnavailable) Error() string {
 func (err ErrServerUnavailable) Error() string {
 	if err.code == 401 {
 	if err.code == 401 {
 		return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.")
 		return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.")
@@ -136,12 +145,12 @@ func translateStatusToError(resp *http.Response, resource string) error {
 	}
 	}
 }
 }
 
 
-// GetMeta downloads the named meta file with the given size. A short body
+// GetSized downloads the named meta file with the given size. A short body
 // is acceptable because in the case of timestamp.json, the size is a cap,
 // is acceptable because in the case of timestamp.json, the size is a cap,
 // not an exact length.
 // not an exact length.
 // If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
 // If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
 // predefined threshold "notary.MaxDownloadSize".
 // predefined threshold "notary.MaxDownloadSize".
-func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
+func (s HTTPStore) GetSized(name string, size int64) ([]byte, error) {
 	url, err := s.buildMetaURL(name)
 	url, err := s.buildMetaURL(name)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -152,7 +161,7 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
 	}
 	}
 	resp, err := s.roundTrip.RoundTrip(req)
 	resp, err := s.roundTrip.RoundTrip(req)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, NetworkError{Wrapped: err}
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	if err := translateStatusToError(resp, name); err != nil {
 	if err := translateStatusToError(resp, name); err != nil {
@@ -174,28 +183,15 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
 	return body, nil
 	return body, nil
 }
 }
 
 
-// SetMeta uploads a piece of TUF metadata to the server
-func (s HTTPStore) SetMeta(name string, blob []byte) error {
-	url, err := s.buildMetaURL("")
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest("POST", url.String(), bytes.NewReader(blob))
-	if err != nil {
-		return err
-	}
-	resp, err := s.roundTrip.RoundTrip(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	return translateStatusToError(resp, "POST "+name)
+// Set sends a single piece of metadata to the TUF server
+func (s HTTPStore) Set(name string, blob []byte) error {
+	return s.SetMulti(map[string][]byte{name: blob})
 }
 }
 
 
-// RemoveMeta always fails, because we should never be able to delete metadata
+// Remove always fails, because we should never be able to delete metadata
 // remotely
 // remotely
-func (s HTTPStore) RemoveMeta(name string) error {
-	return ErrInvalidOperation{msg: "cannot delete metadata"}
+func (s HTTPStore) Remove(name string) error {
+	return ErrInvalidOperation{msg: "cannot delete individual metadata files"}
 }
 }
 
 
 // NewMultiPartMetaRequest builds a request with the provided metadata updates
 // NewMultiPartMetaRequest builds a request with the provided metadata updates
@@ -205,6 +201,9 @@ func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request
 	writer := multipart.NewWriter(body)
 	writer := multipart.NewWriter(body)
 	for role, blob := range metas {
 	for role, blob := range metas {
 		part, err := writer.CreateFormFile("files", role)
 		part, err := writer.CreateFormFile("files", role)
+		if err != nil {
+			return nil, err
+		}
 		_, err = io.Copy(part, bytes.NewBuffer(blob))
 		_, err = io.Copy(part, bytes.NewBuffer(blob))
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -222,10 +221,10 @@ func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request
 	return req, nil
 	return req, nil
 }
 }
 
 
-// SetMultiMeta does a single batch upload of multiple pieces of TUF metadata.
+// SetMulti does a single batch upload of multiple pieces of TUF metadata.
 // This should be preferred for updating a remote server as it enable the server
 // This should be preferred for updating a remote server as it enable the server
 // to remain consistent, either accepting or rejecting the complete update.
 // to remain consistent, either accepting or rejecting the complete update.
-func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
+func (s HTTPStore) SetMulti(metas map[string][]byte) error {
 	url, err := s.buildMetaURL("")
 	url, err := s.buildMetaURL("")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -236,16 +235,29 @@ func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
 	}
 	}
 	resp, err := s.roundTrip.RoundTrip(req)
 	resp, err := s.roundTrip.RoundTrip(req)
 	if err != nil {
 	if err != nil {
-		return err
+		return NetworkError{Wrapped: err}
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	// if this 404's something is pretty wrong
 	// if this 404's something is pretty wrong
 	return translateStatusToError(resp, "POST metadata endpoint")
 	return translateStatusToError(resp, "POST metadata endpoint")
 }
 }
 
 
-// RemoveAll in the interface is not supported, admins should use the DeleteHandler endpoint directly to delete remote data for a GUN
+// RemoveAll will attempt to delete all TUF metadata for a GUN
 func (s HTTPStore) RemoveAll() error {
 func (s HTTPStore) RemoveAll() error {
-	return errors.New("remove all functionality not supported for HTTPStore")
+	url, err := s.buildMetaURL("")
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest("DELETE", url.String(), nil)
+	if err != nil {
+		return err
+	}
+	resp, err := s.roundTrip.RoundTrip(req)
+	if err != nil {
+		return NetworkError{Wrapped: err}
+	}
+	defer resp.Body.Close()
+	return translateStatusToError(resp, "DELETE metadata for GUN endpoint")
 }
 }
 
 
 func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
 func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
@@ -282,9 +294,34 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	resp, err := s.roundTrip.RoundTrip(req)
 	resp, err := s.roundTrip.RoundTrip(req)
+	if err != nil {
+		return nil, NetworkError{Wrapped: err}
+	}
+	defer resp.Body.Close()
+	if err := translateStatusToError(resp, role+" key"); err != nil {
+		return nil, err
+	}
+	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	return body, nil
+}
+
+// RotateKey rotates a private key and returns the public component from the remote server
+func (s HTTPStore) RotateKey(role string) ([]byte, error) {
+	url, err := s.buildKeyURL(role)
+	if err != nil {
+		return nil, err
+	}
+	req, err := http.NewRequest("POST", url.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := s.roundTrip.RoundTrip(req)
+	if err != nil {
+		return nil, NetworkError{Wrapped: err}
+	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 	if err := translateStatusToError(resp, role+" key"); err != nil {
 	if err := translateStatusToError(resp, role+" key"); err != nil {
 		return nil, err
 		return nil, err
@@ -295,3 +332,8 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) {
 	}
 	}
 	return body, nil
 	return body, nil
 }
 }
+
+// Location returns a human readable name for the storage location
+func (s HTTPStore) Location() string {
+	return s.baseURL.String()
+}

+ 13 - 10
vendor/src/github.com/docker/notary/tuf/store/interfaces.go → vendor/src/github.com/docker/notary/storage/interfaces.go

@@ -1,4 +1,4 @@
-package store
+package storage
 
 
 // NoSizeLimit is represented as -1 for arguments to GetMeta
 // NoSizeLimit is represented as -1 for arguments to GetMeta
 const NoSizeLimit int64 = -1
 const NoSizeLimit int64 = -1
@@ -6,21 +6,17 @@ const NoSizeLimit int64 = -1
 // MetadataStore must be implemented by anything that intends to interact
 // MetadataStore must be implemented by anything that intends to interact
 // with a store of TUF files
 // with a store of TUF files
 type MetadataStore interface {
 type MetadataStore interface {
-	GetMeta(name string, size int64) ([]byte, error)
-	SetMeta(name string, blob []byte) error
-	SetMultiMeta(map[string][]byte) error
+	GetSized(name string, size int64) ([]byte, error)
+	Set(name string, blob []byte) error
+	SetMulti(map[string][]byte) error
 	RemoveAll() error
 	RemoveAll() error
-	RemoveMeta(name string) error
+	Remove(name string) error
 }
 }
 
 
 // PublicKeyStore must be implemented by a key service
 // PublicKeyStore must be implemented by a key service
 type PublicKeyStore interface {
 type PublicKeyStore interface {
 	GetKey(role string) ([]byte, error)
 	GetKey(role string) ([]byte, error)
-}
-
-// LocalStore represents a local TUF sture
-type LocalStore interface {
-	MetadataStore
+	RotateKey(role string) ([]byte, error)
 }
 }
 
 
 // RemoteStore is similar to LocalStore with the added expectation that it should
 // RemoteStore is similar to LocalStore with the added expectation that it should
@@ -29,3 +25,10 @@ type RemoteStore interface {
 	MetadataStore
 	MetadataStore
 	PublicKeyStore
 	PublicKeyStore
 }
 }
+
+// Bootstrapper is a thing that can set itself up
+type Bootstrapper interface {
+	// Bootstrap instructs a configured Bootstrapper to perform
+	// its setup operations.
+	Bootstrap() error
+}

+ 46 - 29
vendor/src/github.com/docker/notary/tuf/store/memorystore.go → vendor/src/github.com/docker/notary/storage/memorystore.go

@@ -1,50 +1,46 @@
-package store
+package storage
 
 
 import (
 import (
 	"crypto/sha256"
 	"crypto/sha256"
-	"fmt"
 
 
 	"github.com/docker/notary"
 	"github.com/docker/notary"
-	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/utils"
 	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
 // NewMemoryStore returns a MetadataStore that operates entirely in memory.
 // NewMemoryStore returns a MetadataStore that operates entirely in memory.
 // Very useful for testing
 // Very useful for testing
-func NewMemoryStore(meta map[string][]byte) *MemoryStore {
+func NewMemoryStore(initial map[string][]byte) *MemoryStore {
 	var consistent = make(map[string][]byte)
 	var consistent = make(map[string][]byte)
-	if meta == nil {
-		meta = make(map[string][]byte)
+	if initial == nil {
+		initial = make(map[string][]byte)
 	} else {
 	} else {
 		// add all seed meta to consistent
 		// add all seed meta to consistent
-		for name, data := range meta {
+		for name, data := range initial {
 			checksum := sha256.Sum256(data)
 			checksum := sha256.Sum256(data)
 			path := utils.ConsistentName(name, checksum[:])
 			path := utils.ConsistentName(name, checksum[:])
 			consistent[path] = data
 			consistent[path] = data
 		}
 		}
 	}
 	}
 	return &MemoryStore{
 	return &MemoryStore{
-		meta:       meta,
+		data:       initial,
 		consistent: consistent,
 		consistent: consistent,
-		keys:       make(map[string][]data.PrivateKey),
 	}
 	}
 }
 }
 
 
 // MemoryStore implements a mock RemoteStore entirely in memory.
 // MemoryStore implements a mock RemoteStore entirely in memory.
 // For testing purposes only.
 // For testing purposes only.
 type MemoryStore struct {
 type MemoryStore struct {
-	meta       map[string][]byte
+	data       map[string][]byte
 	consistent map[string][]byte
 	consistent map[string][]byte
-	keys       map[string][]data.PrivateKey
 }
 }
 
 
-// GetMeta returns up to size bytes of data references by name.
+// GetSized returns up to size bytes of data references by name.
 // If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
 // If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
 // predefined threshold "notary.MaxDownloadSize", as we will always know the
 // predefined threshold "notary.MaxDownloadSize", as we will always know the
 // size for everything but a timestamp and sometimes a root,
 // size for everything but a timestamp and sometimes a root,
 // neither of which should be exceptionally large
 // neither of which should be exceptionally large
-func (m *MemoryStore) GetMeta(name string, size int64) ([]byte, error) {
-	d, ok := m.meta[name]
+func (m MemoryStore) GetSized(name string, size int64) ([]byte, error) {
+	d, ok := m.data[name]
 	if ok {
 	if ok {
 		if size == NoSizeLimit {
 		if size == NoSizeLimit {
 			size = notary.MaxDownloadSize
 			size = notary.MaxDownloadSize
@@ -64,9 +60,20 @@ func (m *MemoryStore) GetMeta(name string, size int64) ([]byte, error) {
 	return nil, ErrMetaNotFound{Resource: name}
 	return nil, ErrMetaNotFound{Resource: name}
 }
 }
 
 
-// SetMeta sets the metadata value for the given name
-func (m *MemoryStore) SetMeta(name string, meta []byte) error {
-	m.meta[name] = meta
+// Get returns the data associated with name
+func (m MemoryStore) Get(name string) ([]byte, error) {
+	if d, ok := m.data[name]; ok {
+		return d, nil
+	}
+	if d, ok := m.consistent[name]; ok {
+		return d, nil
+	}
+	return nil, ErrMetaNotFound{Resource: name}
+}
+
+// Set sets the metadata value for the given name
+func (m *MemoryStore) Set(name string, meta []byte) error {
+	m.data[name] = meta
 
 
 	checksum := sha256.Sum256(meta)
 	checksum := sha256.Sum256(meta)
 	path := utils.ConsistentName(name, checksum[:])
 	path := utils.ConsistentName(name, checksum[:])
@@ -74,34 +81,44 @@ func (m *MemoryStore) SetMeta(name string, meta []byte) error {
 	return nil
 	return nil
 }
 }
 
 
-// SetMultiMeta sets multiple pieces of metadata for multiple names
+// SetMulti sets multiple pieces of metadata for multiple names
 // in a single operation.
 // in a single operation.
-func (m *MemoryStore) SetMultiMeta(metas map[string][]byte) error {
+func (m *MemoryStore) SetMulti(metas map[string][]byte) error {
 	for role, blob := range metas {
 	for role, blob := range metas {
-		m.SetMeta(role, blob)
+		m.Set(role, blob)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// RemoveMeta removes the metadata for a single role - if the metadata doesn't
+// Remove removes the metadata for a single role - if the metadata doesn't
 // exist, no error is returned
 // exist, no error is returned
-func (m *MemoryStore) RemoveMeta(name string) error {
-	if meta, ok := m.meta[name]; ok {
+func (m *MemoryStore) Remove(name string) error {
+	if meta, ok := m.data[name]; ok {
 		checksum := sha256.Sum256(meta)
 		checksum := sha256.Sum256(meta)
 		path := utils.ConsistentName(name, checksum[:])
 		path := utils.ConsistentName(name, checksum[:])
-		delete(m.meta, name)
+		delete(m.data, name)
 		delete(m.consistent, path)
 		delete(m.consistent, path)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-// GetKey returns the public key for the given role
-func (m *MemoryStore) GetKey(role string) ([]byte, error) {
-	return nil, fmt.Errorf("GetKey is not implemented for the MemoryStore")
-}
-
 // RemoveAll clears the existing memory store by setting this store as new empty one
 // RemoveAll clears the existing memory store by setting this store as new empty one
 func (m *MemoryStore) RemoveAll() error {
 func (m *MemoryStore) RemoveAll() error {
 	*m = *NewMemoryStore(nil)
 	*m = *NewMemoryStore(nil)
 	return nil
 	return nil
 }
 }
+
+// Location provides a human readable name for the storage location
+func (m MemoryStore) Location() string {
+	return "memory"
+}
+
+// ListFiles returns a list of all files. The names returned should be
+// usable with Get directly, with no modification.
+func (m *MemoryStore) ListFiles() []string {
+	names := make([]string, 0, len(m.data))
+	for n := range m.data {
+		names = append(names, n)
+	}
+	return names
+}

+ 16 - 15
vendor/src/github.com/docker/notary/tuf/store/offlinestore.go → vendor/src/github.com/docker/notary/storage/offlinestore.go

@@ -1,8 +1,4 @@
-package store
-
-import (
-	"io"
-)
+package storage
 
 
 // ErrOffline is used to indicate we are operating offline
 // ErrOffline is used to indicate we are operating offline
 type ErrOffline struct{}
 type ErrOffline struct{}
@@ -17,23 +13,23 @@ var err = ErrOffline{}
 // returns ErrOffline for every operation
 // returns ErrOffline for every operation
 type OfflineStore struct{}
 type OfflineStore struct{}
 
 
-// GetMeta returns ErrOffline
-func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) {
+// GetSized returns ErrOffline
+func (es OfflineStore) GetSized(name string, size int64) ([]byte, error) {
 	return nil, err
 	return nil, err
 }
 }
 
 
-// SetMeta returns ErrOffline
-func (es OfflineStore) SetMeta(name string, blob []byte) error {
+// Set returns ErrOffline
+func (es OfflineStore) Set(name string, blob []byte) error {
 	return err
 	return err
 }
 }
 
 
-// SetMultiMeta returns ErrOffline
-func (es OfflineStore) SetMultiMeta(map[string][]byte) error {
+// SetMulti returns ErrOffline
+func (es OfflineStore) SetMulti(map[string][]byte) error {
 	return err
 	return err
 }
 }
 
 
-// RemoveMeta returns ErrOffline
-func (es OfflineStore) RemoveMeta(name string) error {
+// Remove returns ErrOffline
+func (es OfflineStore) Remove(name string) error {
 	return err
 	return err
 }
 }
 
 
@@ -42,8 +38,8 @@ func (es OfflineStore) GetKey(role string) ([]byte, error) {
 	return nil, err
 	return nil, err
 }
 }
 
 
-// GetTarget returns ErrOffline
-func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) {
+// RotateKey returns ErrOffline
+func (es OfflineStore) RotateKey(role string) ([]byte, error) {
 	return nil, err
 	return nil, err
 }
 }
 
 
@@ -51,3 +47,8 @@ func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) {
 func (es OfflineStore) RemoveAll() error {
 func (es OfflineStore) RemoveAll() error {
 	return err
 	return err
 }
 }
+
+// Location returns a human readable name for the storage location
+func (es OfflineStore) Location() string {
+	return "offline"
+}

+ 0 - 150
vendor/src/github.com/docker/notary/trustmanager/filestore.go

@@ -1,150 +0,0 @@
-package trustmanager
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-)
-
-// SimpleFileStore implements FileStore
-type SimpleFileStore struct {
-	baseDir string
-	fileExt string
-	perms   os.FileMode
-}
-
-// NewFileStore creates a fully configurable file store
-func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*SimpleFileStore, error) {
-	baseDir = filepath.Clean(baseDir)
-	if err := createDirectory(baseDir, perms); err != nil {
-		return nil, err
-	}
-	if !strings.HasPrefix(fileExt, ".") {
-		fileExt = "." + fileExt
-	}
-
-	return &SimpleFileStore{
-		baseDir: baseDir,
-		fileExt: fileExt,
-		perms:   perms,
-	}, nil
-}
-
-// NewSimpleFileStore is a convenience wrapper to create a world readable,
-// owner writeable filestore
-func NewSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
-	return NewFileStore(baseDir, fileExt, visible)
-}
-
-// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
-// _only_ filestore
-func NewPrivateSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
-	return NewFileStore(baseDir, fileExt, private)
-}
-
-// Add writes data to a file with a given name
-func (f *SimpleFileStore) Add(name string, data []byte) error {
-	filePath, err := f.GetPath(name)
-	if err != nil {
-		return err
-	}
-	createDirectory(filepath.Dir(filePath), f.perms)
-	return ioutil.WriteFile(filePath, data, f.perms)
-}
-
-// Remove removes a file identified by name
-func (f *SimpleFileStore) Remove(name string) error {
-	// Attempt to remove
-	filePath, err := f.GetPath(name)
-	if err != nil {
-		return err
-	}
-	return os.Remove(filePath)
-}
-
-// Get returns the data given a file name
-func (f *SimpleFileStore) Get(name string) ([]byte, error) {
-	filePath, err := f.GetPath(name)
-	if err != nil {
-		return nil, err
-	}
-	data, err := ioutil.ReadFile(filePath)
-	if err != nil {
-		return nil, err
-	}
-
-	return data, nil
-}
-
-// GetPath returns the full final path of a file with a given name
-func (f *SimpleFileStore) GetPath(name string) (string, error) {
-	fileName := f.genFileName(name)
-	fullPath := filepath.Clean(filepath.Join(f.baseDir, fileName))
-
-	if !strings.HasPrefix(fullPath, f.baseDir) {
-		return "", ErrPathOutsideStore
-	}
-	return fullPath, nil
-}
-
-// ListFiles lists all the files inside of a store
-func (f *SimpleFileStore) ListFiles() []string {
-	return f.list(f.baseDir)
-}
-
-// list lists all the files in a directory given a full path. Ignores symlinks.
-func (f *SimpleFileStore) list(path string) []string {
-	files := make([]string, 0, 0)
-	filepath.Walk(path, func(fp string, fi os.FileInfo, err error) error {
-		// If there are errors, ignore this particular file
-		if err != nil {
-			return nil
-		}
-		// Ignore if it is a directory
-		if fi.IsDir() {
-			return nil
-		}
-
-		// If this is a symlink, ignore it
-		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
-			return nil
-		}
-
-		// Only allow matches that end with our certificate extension (e.g. *.crt)
-		matched, _ := filepath.Match("*"+f.fileExt, fi.Name())
-
-		if matched {
-			// Find the relative path for this file relative to the base path.
-			fp, err = filepath.Rel(path, fp)
-			if err != nil {
-				return err
-			}
-			trimmed := strings.TrimSuffix(fp, f.fileExt)
-			files = append(files, trimmed)
-		}
-		return nil
-	})
-	return files
-}
-
-// genFileName returns the name using the right extension
-func (f *SimpleFileStore) genFileName(name string) string {
-	return fmt.Sprintf("%s%s", name, f.fileExt)
-}
-
-// BaseDir returns the base directory of the filestore
-func (f *SimpleFileStore) BaseDir() string {
-	return f.baseDir
-}
-
-// createDirectory receives a string of the path to a directory.
-// It does not support passing files, so the caller has to remove
-// the filename by doing filepath.Dir(full_path_to_file)
-func createDirectory(dir string, perms os.FileMode) error {
-	// This prevents someone passing /path/to/dir and 'dir' not being created
-	// If two '//' exist, MkdirAll deals it with correctly
-	dir = dir + "/"
-	return os.MkdirAll(dir, perms)
-}

+ 82 - 0
vendor/src/github.com/docker/notary/trustmanager/interfaces.go

@@ -0,0 +1,82 @@
+package trustmanager
+
+import (
+	"fmt"
+
+	"github.com/docker/notary/tuf/data"
+)
+
+// Storage implements the bare bones primitives (no hierarchy)
+type Storage interface {
+	// Add writes a file to the specified location, returning an error if this
+	// is not possible (reasons may include permissions errors). The path is cleaned
+	// before being made absolute against the store's base dir.
+	Set(fileName string, data []byte) error
+
+	// Remove deletes a file from the store relative to the store's base directory.
+	// The path is cleaned before being made absolute to ensure no path traversal
+	// outside the base directory is possible.
+	Remove(fileName string) error
+
+	// Get returns the file content found at fileName relative to the base directory
+	// of the file store. The path is cleaned before being made absolute to ensure
+	// path traversal outside the store is not possible. If the file is not found
+	// an error to that effect is returned.
+	Get(fileName string) ([]byte, error)
+
+	// ListFiles returns a list of paths relative to the base directory of the
+	// filestore. Any of these paths must be retrievable via the
+	// Storage.Get method.
+	ListFiles() []string
+
+	// Location returns a human readable name indicating where the implementer
+	// is storing keys
+	Location() string
+}
+
+// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
+type ErrAttemptsExceeded struct{}
+
+// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
+func (err ErrAttemptsExceeded) Error() string {
+	return "maximum number of passphrase attempts exceeded"
+}
+
+// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
+// key file was corrupted, but we have no way to distinguish.
+type ErrPasswordInvalid struct{}
+
+// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
+// key file was corrupted, but we have no way to distinguish.
+func (err ErrPasswordInvalid) Error() string {
+	return "password invalid, operation has failed."
+}
+
+// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
+type ErrKeyNotFound struct {
+	KeyID string
+}
+
+// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
+func (err ErrKeyNotFound) Error() string {
+	return fmt.Sprintf("signing key not found: %s", err.KeyID)
+}
+
+// KeyStore is a generic interface for private key storage
+type KeyStore interface {
+	// AddKey adds a key to the KeyStore, and if the key already exists,
+	// succeeds.  Otherwise, returns an error if it cannot add.
+	AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
+	// Should fail with ErrKeyNotFound if the keystore is operating normally
+	// and knows that it does not store the requested key.
+	GetKey(keyID string) (data.PrivateKey, string, error)
+	GetKeyInfo(keyID string) (KeyInfo, error)
+	ListKeys() map[string]KeyInfo
+	RemoveKey(keyID string) error
+	Name() string
+}
+
+type cachedKey struct {
+	alias string
+	key   data.PrivateKey
+}

+ 0 - 497
vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go

@@ -1,497 +0,0 @@
-package trustmanager
-
-import (
-	"encoding/pem"
-	"fmt"
-	"path/filepath"
-	"strings"
-	"sync"
-
-	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary"
-	"github.com/docker/notary/passphrase"
-	"github.com/docker/notary/tuf/data"
-)
-
-type keyInfoMap map[string]KeyInfo
-
-// KeyFileStore persists and manages private keys on disk
-type KeyFileStore struct {
-	sync.Mutex
-	SimpleFileStore
-	passphrase.Retriever
-	cachedKeys map[string]*cachedKey
-	keyInfoMap
-}
-
-// KeyMemoryStore manages private keys in memory
-type KeyMemoryStore struct {
-	sync.Mutex
-	MemoryFileStore
-	passphrase.Retriever
-	cachedKeys map[string]*cachedKey
-	keyInfoMap
-}
-
-// KeyInfo stores the role, path, and gun for a corresponding private key ID
-// It is assumed that each private key ID is unique
-type KeyInfo struct {
-	Gun  string
-	Role string
-}
-
-// NewKeyFileStore returns a new KeyFileStore creating a private directory to
-// hold the keys.
-func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
-	baseDir = filepath.Join(baseDir, notary.PrivDir)
-	fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
-	if err != nil {
-		return nil, err
-	}
-	cachedKeys := make(map[string]*cachedKey)
-	keyInfoMap := make(keyInfoMap)
-
-	keyStore := &KeyFileStore{SimpleFileStore: *fileStore,
-		Retriever:  passphraseRetriever,
-		cachedKeys: cachedKeys,
-		keyInfoMap: keyInfoMap,
-	}
-
-	// Load this keystore's ID --> gun/role map
-	keyStore.loadKeyInfo()
-	return keyStore, nil
-}
-
-func generateKeyInfoMap(s Storage) map[string]KeyInfo {
-	keyInfoMap := make(map[string]KeyInfo)
-	for _, keyPath := range s.ListFiles() {
-		d, err := s.Get(keyPath)
-		if err != nil {
-			logrus.Error(err)
-			continue
-		}
-		keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
-		if err != nil {
-			logrus.Error(err)
-			continue
-		}
-		keyInfoMap[keyID] = keyInfo
-	}
-	return keyInfoMap
-}
-
-// Attempts to infer the keyID, role, and GUN from the specified key path.
-// Note that non-root roles can only be inferred if this is a legacy style filename: KEYID_ROLE.key
-func inferKeyInfoFromKeyPath(keyPath string) (string, string, string) {
-	var keyID, role, gun string
-	keyID = filepath.Base(keyPath)
-	underscoreIndex := strings.LastIndex(keyID, "_")
-
-	// This is the legacy KEYID_ROLE filename
-	// The keyID is the first part of the keyname
-	// The keyRole is the second part of the keyname
-	// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
-	if underscoreIndex != -1 {
-		role = keyID[underscoreIndex+1:]
-		keyID = keyID[:underscoreIndex]
-	}
-
-	if filepath.HasPrefix(keyPath, notary.RootKeysSubdir+"/") {
-		return keyID, data.CanonicalRootRole, ""
-	}
-
-	keyPath = strings.TrimPrefix(keyPath, notary.NonRootKeysSubdir+"/")
-	gun = getGunFromFullID(keyPath)
-	return keyID, role, gun
-}
-
-func getGunFromFullID(fullKeyID string) string {
-	keyGun := filepath.Dir(fullKeyID)
-	// If the gun is empty, Dir will return .
-	if keyGun == "." {
-		keyGun = ""
-	}
-	return keyGun
-}
-
-func (s *KeyFileStore) loadKeyInfo() {
-	s.keyInfoMap = generateKeyInfoMap(s)
-}
-
-func (s *KeyMemoryStore) loadKeyInfo() {
-	s.keyInfoMap = generateKeyInfoMap(s)
-}
-
-// GetKeyInfo returns the corresponding gun and role key info for a keyID
-func (s *KeyFileStore) GetKeyInfo(keyID string) (KeyInfo, error) {
-	if info, ok := s.keyInfoMap[keyID]; ok {
-		return info, nil
-	}
-	return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
-}
-
-// GetKeyInfo returns the corresponding gun and role key info for a keyID
-func (s *KeyMemoryStore) GetKeyInfo(keyID string) (KeyInfo, error) {
-	if info, ok := s.keyInfoMap[keyID]; ok {
-		return info, nil
-	}
-	return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
-}
-
-// Name returns a user friendly name for the location this store
-// keeps its data
-func (s *KeyFileStore) Name() string {
-	return fmt.Sprintf("file (%s)", s.SimpleFileStore.BaseDir())
-}
-
-// AddKey stores the contents of a PEM-encoded private key as a PEM block
-func (s *KeyFileStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
-	s.Lock()
-	defer s.Unlock()
-	if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
-		keyInfo.Gun = ""
-	}
-	err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
-	if err != nil {
-		return err
-	}
-	s.keyInfoMap[privKey.ID()] = keyInfo
-	return nil
-}
-
-// GetKey returns the PrivateKey given a KeyID
-func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) {
-	s.Lock()
-	defer s.Unlock()
-	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
-	if keyInfo, ok := s.keyInfoMap[name]; ok {
-		name = filepath.Join(keyInfo.Gun, name)
-	}
-	return getKey(s, s.Retriever, s.cachedKeys, name)
-}
-
-// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
-func (s *KeyFileStore) ListKeys() map[string]KeyInfo {
-	return copyKeyInfoMap(s.keyInfoMap)
-}
-
-// RemoveKey removes the key from the keyfilestore
-func (s *KeyFileStore) RemoveKey(keyID string) error {
-	s.Lock()
-	defer s.Unlock()
-	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
-	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
-		keyID = filepath.Join(keyInfo.Gun, keyID)
-	}
-	err := removeKey(s, s.cachedKeys, keyID)
-	if err != nil {
-		return err
-	}
-	// Remove this key from our keyInfo map if we removed from our filesystem
-	delete(s.keyInfoMap, filepath.Base(keyID))
-	return nil
-}
-
-// ExportKey exports the encrypted bytes from the keystore
-func (s *KeyFileStore) ExportKey(keyID string) ([]byte, error) {
-	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
-		keyID = filepath.Join(keyInfo.Gun, keyID)
-	}
-	keyBytes, _, err := getRawKey(s, keyID)
-	if err != nil {
-		return nil, err
-	}
-	return keyBytes, nil
-}
-
-// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
-func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
-	memStore := NewMemoryFileStore()
-	cachedKeys := make(map[string]*cachedKey)
-
-	keyInfoMap := make(keyInfoMap)
-
-	keyStore := &KeyMemoryStore{MemoryFileStore: *memStore,
-		Retriever:  passphraseRetriever,
-		cachedKeys: cachedKeys,
-		keyInfoMap: keyInfoMap,
-	}
-
-	// Load this keystore's ID --> gun/role map
-	keyStore.loadKeyInfo()
-	return keyStore
-}
-
-// Name returns a user friendly name for the location this store
-// keeps its data
-func (s *KeyMemoryStore) Name() string {
-	return "memory"
-}
-
-// AddKey stores the contents of a PEM-encoded private key as a PEM block
-func (s *KeyMemoryStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
-	s.Lock()
-	defer s.Unlock()
-	if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
-		keyInfo.Gun = ""
-	}
-	err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
-	if err != nil {
-		return err
-	}
-	s.keyInfoMap[privKey.ID()] = keyInfo
-	return nil
-}
-
-// GetKey returns the PrivateKey given a KeyID
-func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) {
-	s.Lock()
-	defer s.Unlock()
-	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
-	if keyInfo, ok := s.keyInfoMap[name]; ok {
-		name = filepath.Join(keyInfo.Gun, name)
-	}
-	return getKey(s, s.Retriever, s.cachedKeys, name)
-}
-
-// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
-func (s *KeyMemoryStore) ListKeys() map[string]KeyInfo {
-	return copyKeyInfoMap(s.keyInfoMap)
-}
-
-// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
-func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
-	copyMap := make(map[string]KeyInfo)
-	for keyID, keyInfo := range keyInfoMap {
-		copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
-	}
-	return copyMap
-}
-
-// RemoveKey removes the key from the keystore
-func (s *KeyMemoryStore) RemoveKey(keyID string) error {
-	s.Lock()
-	defer s.Unlock()
-	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
-	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
-		keyID = filepath.Join(keyInfo.Gun, keyID)
-	}
-	err := removeKey(s, s.cachedKeys, keyID)
-	if err != nil {
-		return err
-	}
-	// Remove this key from our keyInfo map if we removed from our filesystem
-	delete(s.keyInfoMap, filepath.Base(keyID))
-	return nil
-}
-
-// ExportKey exports the encrypted bytes from the keystore
-func (s *KeyMemoryStore) ExportKey(keyID string) ([]byte, error) {
-	keyBytes, _, err := getRawKey(s, keyID)
-	if err != nil {
-		return nil, err
-	}
-	return keyBytes, nil
-}
-
-// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
-func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
-	keyID, role, gun := inferKeyInfoFromKeyPath(filename)
-	if role == "" {
-		block, _ := pem.Decode(pemBytes)
-		if block == nil {
-			return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
-		}
-		if keyRole, ok := block.Headers["role"]; ok {
-			role = keyRole
-		}
-	}
-	return keyID, KeyInfo{Gun: gun, Role: role}, nil
-}
-
-func addKey(s Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
-
-	var (
-		chosenPassphrase string
-		giveup           bool
-		err              error
-	)
-
-	for attempts := 0; ; attempts++ {
-		chosenPassphrase, giveup, err = passphraseRetriever(name, role, true, attempts)
-		if err != nil {
-			continue
-		}
-		if giveup {
-			return ErrAttemptsExceeded{}
-		}
-		if attempts > 10 {
-			return ErrAttemptsExceeded{}
-		}
-		break
-	}
-
-	return encryptAndAddKey(s, chosenPassphrase, cachedKeys, name, role, privKey)
-}
-
-// getKeyRole finds the role for the given keyID. It attempts to look
-// both in the newer format PEM headers, and also in the legacy filename
-// format. It returns: the role, whether it was found in the legacy format
-// (true == legacy), and an error
-func getKeyRole(s Storage, keyID string) (string, bool, error) {
-	name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
-
-	for _, file := range s.ListFiles() {
-		filename := filepath.Base(file)
-
-		if strings.HasPrefix(filename, name) {
-			d, err := s.Get(file)
-			if err != nil {
-				return "", false, err
-			}
-			block, _ := pem.Decode(d)
-			if block != nil {
-				if role, ok := block.Headers["role"]; ok {
-					return role, false, nil
-				}
-			}
-
-			role := strings.TrimPrefix(filename, name+"_")
-			return role, true, nil
-		}
-	}
-
-	return "", false, ErrKeyNotFound{KeyID: keyID}
-}
-
-// GetKey returns the PrivateKey given a KeyID
-func getKey(s Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
-	cachedKeyEntry, ok := cachedKeys[name]
-	if ok {
-		return cachedKeyEntry.key, cachedKeyEntry.alias, nil
-	}
-
-	keyBytes, keyAlias, err := getRawKey(s, name)
-	if err != nil {
-		return nil, "", err
-	}
-
-	// See if the key is encrypted. If its encrypted we'll fail to parse the private key
-	privKey, err := ParsePEMPrivateKey(keyBytes, "")
-	if err != nil {
-		privKey, _, err = GetPasswdDecryptBytes(passphraseRetriever, keyBytes, name, string(keyAlias))
-		if err != nil {
-			return nil, "", err
-		}
-	}
-	cachedKeys[name] = &cachedKey{alias: keyAlias, key: privKey}
-	return privKey, keyAlias, nil
-}
-
-// RemoveKey removes the key from the keyfilestore
-func removeKey(s Storage, cachedKeys map[string]*cachedKey, name string) error {
-	role, legacy, err := getKeyRole(s, name)
-	if err != nil {
-		return err
-	}
-
-	delete(cachedKeys, name)
-
-	if legacy {
-		name = name + "_" + role
-	}
-
-	// being in a subdirectory is for backwards compatibliity
-	err = s.Remove(filepath.Join(getSubdir(role), name))
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
-func getSubdir(alias string) string {
-	if alias == data.CanonicalRootRole {
-		return notary.RootKeysSubdir
-	}
-	return notary.NonRootKeysSubdir
-}
-
-// Given a key ID, gets the bytes and alias belonging to that key if the key
-// exists
-func getRawKey(s Storage, name string) ([]byte, string, error) {
-	role, legacy, err := getKeyRole(s, name)
-	if err != nil {
-		return nil, "", err
-	}
-
-	if legacy {
-		name = name + "_" + role
-	}
-
-	var keyBytes []byte
-	keyBytes, err = s.Get(filepath.Join(getSubdir(role), name))
-	if err != nil {
-		return nil, "", err
-	}
-	return keyBytes, role, nil
-}
-
-// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
-// Returns the password and private key
-func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
-	var (
-		passwd  string
-		retErr  error
-		privKey data.PrivateKey
-	)
-	for attempts := 0; ; attempts++ {
-		var (
-			giveup bool
-			err    error
-		)
-		passwd, giveup, err = passphraseRetriever(name, alias, false, attempts)
-		// Check if the passphrase retriever got an error or if it is telling us to give up
-		if giveup || err != nil {
-			return nil, "", ErrPasswordInvalid{}
-		}
-		if attempts > 10 {
-			return nil, "", ErrAttemptsExceeded{}
-		}
-
-		// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
-		privKey, err = ParsePEMPrivateKey(pemBytes, passwd)
-		if err != nil {
-			retErr = ErrPasswordInvalid{}
-		} else {
-			// We managed to parse the PrivateKey. We've succeeded!
-			retErr = nil
-			break
-		}
-	}
-	if retErr != nil {
-		return nil, "", retErr
-	}
-	return privKey, passwd, nil
-}
-
-func encryptAndAddKey(s Storage, passwd string, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
-
-	var (
-		pemPrivKey []byte
-		err        error
-	)
-
-	if passwd != "" {
-		pemPrivKey, err = EncryptPrivateKey(privKey, role, passwd)
-	} else {
-		pemPrivKey, err = KeyToPEM(privKey, role)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	cachedKeys[name] = &cachedKey{alias: role, key: privKey}
-	return s.Add(filepath.Join(getSubdir(role), name), pemPrivKey)
-}

+ 305 - 39
vendor/src/github.com/docker/notary/trustmanager/keystore.go

@@ -1,59 +1,325 @@
 package trustmanager
 package trustmanager
 
 
 import (
 import (
+	"encoding/pem"
 	"fmt"
 	"fmt"
+	"path/filepath"
+	"strings"
+	"sync"
 
 
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/notary"
+	store "github.com/docker/notary/storage"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
-// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
-type ErrAttemptsExceeded struct{}
+type keyInfoMap map[string]KeyInfo
 
 
-// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
-func (err ErrAttemptsExceeded) Error() string {
-	return "maximum number of passphrase attempts exceeded"
+// KeyInfo stores the role, path, and gun for a corresponding private key ID
+// It is assumed that each private key ID is unique
+type KeyInfo struct {
+	Gun  string
+	Role string
 }
 }
 
 
-// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
-// key file was corrupted, but we have no way to distinguish.
-type ErrPasswordInvalid struct{}
+// GenericKeyStore is a wrapper for Storage instances that provides
+// translation between the []byte form and Public/PrivateKey objects
+type GenericKeyStore struct {
+	store Storage
+	sync.Mutex
+	notary.PassRetriever
+	cachedKeys map[string]*cachedKey
+	keyInfoMap
+}
 
 
-// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
-// key file was corrupted, but we have no way to distinguish.
-func (err ErrPasswordInvalid) Error() string {
-	return "password invalid, operation has failed."
+// NewKeyFileStore returns a new KeyFileStore creating a private directory to
+// hold the keys.
+func NewKeyFileStore(baseDir string, p notary.PassRetriever) (*GenericKeyStore, error) {
+	fileStore, err := store.NewPrivateKeyFileStorage(baseDir, notary.KeyExtension)
+	if err != nil {
+		return nil, err
+	}
+	return NewGenericKeyStore(fileStore, p), nil
 }
 }
 
 
-// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
-type ErrKeyNotFound struct {
-	KeyID string
+// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
+func NewKeyMemoryStore(p notary.PassRetriever) *GenericKeyStore {
+	memStore := store.NewMemoryStore(nil)
+	return NewGenericKeyStore(memStore, p)
 }
 }
 
 
-// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
-func (err ErrKeyNotFound) Error() string {
-	return fmt.Sprintf("signing key not found: %s", err.KeyID)
+// NewGenericKeyStore creates a GenericKeyStore wrapping the provided
+// Storage instance, using the PassRetriever to enc/decrypt keys
+func NewGenericKeyStore(s Storage, p notary.PassRetriever) *GenericKeyStore {
+	ks := GenericKeyStore{
+		store:         s,
+		PassRetriever: p,
+		cachedKeys:    make(map[string]*cachedKey),
+		keyInfoMap:    make(keyInfoMap),
+	}
+	ks.loadKeyInfo()
+	return &ks
 }
 }
 
 
-const (
-	keyExtension = "key"
-)
+func generateKeyInfoMap(s Storage) map[string]KeyInfo {
+	keyInfoMap := make(map[string]KeyInfo)
+	for _, keyPath := range s.ListFiles() {
+		d, err := s.Get(keyPath)
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+		keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
+		if err != nil {
+			logrus.Error(err)
+			continue
+		}
+		keyInfoMap[keyID] = keyInfo
+	}
+	return keyInfoMap
+}
+
+// Attempts to infer the keyID, role, and GUN from the specified key path.
+// Note that non-root roles can only be inferred if this is a legacy style filename: KEYID_ROLE.key
+func inferKeyInfoFromKeyPath(keyPath string) (string, string, string) {
+	var keyID, role, gun string
+	keyID = filepath.Base(keyPath)
+	underscoreIndex := strings.LastIndex(keyID, "_")
+
+	// This is the legacy KEYID_ROLE filename
+	// The keyID is the first part of the keyname
+	// The keyRole is the second part of the keyname
+	// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
+	if underscoreIndex != -1 {
+		role = keyID[underscoreIndex+1:]
+		keyID = keyID[:underscoreIndex]
+	}
+
+	if filepath.HasPrefix(keyPath, notary.RootKeysSubdir+"/") {
+		return keyID, data.CanonicalRootRole, ""
+	}
+
+	keyPath = strings.TrimPrefix(keyPath, notary.NonRootKeysSubdir+"/")
+	gun = getGunFromFullID(keyPath)
+	return keyID, role, gun
+}
+
+func getGunFromFullID(fullKeyID string) string {
+	keyGun := filepath.Dir(fullKeyID)
+	// If the gun is empty, Dir will return .
+	if keyGun == "." {
+		keyGun = ""
+	}
+	return keyGun
+}
+
+func (s *GenericKeyStore) loadKeyInfo() {
+	s.keyInfoMap = generateKeyInfoMap(s.store)
+}
+
+// GetKeyInfo returns the corresponding gun and role key info for a keyID
+func (s *GenericKeyStore) GetKeyInfo(keyID string) (KeyInfo, error) {
+	if info, ok := s.keyInfoMap[keyID]; ok {
+		return info, nil
+	}
+	return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
+}
+
+// AddKey stores the contents of a PEM-encoded private key as a PEM block
+func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
+	var (
+		chosenPassphrase string
+		giveup           bool
+		err              error
+		pemPrivKey       []byte
+	)
+	s.Lock()
+	defer s.Unlock()
+	if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
+		keyInfo.Gun = ""
+	}
+	name := filepath.Join(keyInfo.Gun, privKey.ID())
+	for attempts := 0; ; attempts++ {
+		chosenPassphrase, giveup, err = s.PassRetriever(name, keyInfo.Role, true, attempts)
+		if err == nil {
+			break
+		}
+		if giveup || attempts > 10 {
+			return ErrAttemptsExceeded{}
+		}
+	}
+
+	if chosenPassphrase != "" {
+		pemPrivKey, err = utils.EncryptPrivateKey(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
+	} else {
+		pemPrivKey, err = utils.KeyToPEM(privKey, keyInfo.Role)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	s.cachedKeys[name] = &cachedKey{alias: keyInfo.Role, key: privKey}
+	err = s.store.Set(filepath.Join(getSubdir(keyInfo.Role), name), pemPrivKey)
+	if err != nil {
+		return err
+	}
+	s.keyInfoMap[privKey.ID()] = keyInfo
+	return nil
+}
+
+// GetKey returns the PrivateKey given a KeyID
+func (s *GenericKeyStore) GetKey(name string) (data.PrivateKey, string, error) {
+	s.Lock()
+	defer s.Unlock()
+	cachedKeyEntry, ok := s.cachedKeys[name]
+	if ok {
+		return cachedKeyEntry.key, cachedKeyEntry.alias, nil
+	}
+
+	keyBytes, _, keyAlias, err := getKey(s.store, name)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// See if the key is encrypted. If its encrypted we'll fail to parse the private key
+	privKey, err := utils.ParsePEMPrivateKey(keyBytes, "")
+	if err != nil {
+		privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, name, string(keyAlias))
+		if err != nil {
+			return nil, "", err
+		}
+	}
+	s.cachedKeys[name] = &cachedKey{alias: keyAlias, key: privKey}
+	return privKey, keyAlias, nil
+}
+
+// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
+func (s *GenericKeyStore) ListKeys() map[string]KeyInfo {
+	return copyKeyInfoMap(s.keyInfoMap)
+}
+
+// RemoveKey removes the key from the keyfilestore
+func (s *GenericKeyStore) RemoveKey(keyID string) error {
+	s.Lock()
+	defer s.Unlock()
+
+	_, filename, _, err := getKey(s.store, keyID)
+	switch err.(type) {
+	case ErrKeyNotFound, nil:
+		break
+	default:
+		return err
+	}
+
+	delete(s.cachedKeys, keyID)
+
+	err = s.store.Remove(filename) // removing a file that doesn't exist doesn't fail
+	if err != nil {
+		return err
+	}
+
+	// Remove this key from our keyInfo map if we removed from our filesystem
+	delete(s.keyInfoMap, filepath.Base(keyID))
+	return nil
+}
+
+// Name returns a user friendly name for the location this store
+// keeps its data
+func (s *GenericKeyStore) Name() string {
+	return s.store.Location()
+}
+
+// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
+func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
+	copyMap := make(map[string]KeyInfo)
+	for keyID, keyInfo := range keyInfoMap {
+		copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
+	}
+	return copyMap
+}
+
+// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
+func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
+	keyID, role, gun := inferKeyInfoFromKeyPath(filename)
+	if role == "" {
+		block, _ := pem.Decode(pemBytes)
+		if block == nil {
+			return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
+		}
+		if keyRole, ok := block.Headers["role"]; ok {
+			role = keyRole
+		}
+	}
+	return keyID, KeyInfo{Gun: gun, Role: role}, nil
+}
+
+// getKey finds the key and role for the given keyID. It attempts to
+// look both in the newer format PEM headers, and also in the legacy filename
+// format. It returns: the key bytes, the filename it was found under, the role,
+// and an error
+func getKey(s Storage, keyID string) ([]byte, string, string, error) {
+	name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
+
+	for _, file := range s.ListFiles() {
+		filename := filepath.Base(file)
+
+		if strings.HasPrefix(filename, name) {
+			d, err := s.Get(file)
+			if err != nil {
+				return nil, "", "", err
+			}
+			block, _ := pem.Decode(d)
+			if block != nil {
+				if role, ok := block.Headers["role"]; ok {
+					return d, file, role, nil
+				}
+			}
+
+			role := strings.TrimPrefix(filename, name+"_")
+			return d, file, role, nil
+		}
+	}
+
+	return nil, "", "", ErrKeyNotFound{KeyID: keyID}
+}
+
+// Assumes 2 subdirectories, 1 containing root keys and 1 containing TUF keys
+func getSubdir(alias string) string {
+	if alias == data.CanonicalRootRole {
+		return notary.RootKeysSubdir
+	}
+	return notary.NonRootKeysSubdir
+}
+
+// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
+// Returns the password and private key
+func GetPasswdDecryptBytes(passphraseRetriever notary.PassRetriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
+	var (
+		passwd  string
+		privKey data.PrivateKey
+	)
+	for attempts := 0; ; attempts++ {
+		var (
+			giveup bool
+			err    error
+		)
+		if attempts > 10 {
+			return nil, "", ErrAttemptsExceeded{}
+		}
+		passwd, giveup, err = passphraseRetriever(name, alias, false, attempts)
+		// Check if the passphrase retriever got an error or if it is telling us to give up
+		if giveup || err != nil {
+			return nil, "", ErrPasswordInvalid{}
+		}
 
 
-// KeyStore is a generic interface for private key storage
-type KeyStore interface {
-	// AddKey adds a key to the KeyStore, and if the key already exists,
-	// succeeds.  Otherwise, returns an error if it cannot add.
-	AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
-	// Should fail with ErrKeyNotFound if the keystore is operating normally
-	// and knows that it does not store the requested key.
-	GetKey(keyID string) (data.PrivateKey, string, error)
-	GetKeyInfo(keyID string) (KeyInfo, error)
-	ListKeys() map[string]KeyInfo
-	RemoveKey(keyID string) error
-	ExportKey(keyID string) ([]byte, error)
-	Name() string
-}
-
-type cachedKey struct {
-	alias string
-	key   data.PrivateKey
+		// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
+		privKey, err = utils.ParsePEMPrivateKey(pemBytes, passwd)
+		if err == nil {
+			// We managed to parse the PrivateKey. We've succeeded!
+			break
+		}
+	}
+	return privKey, passwd, nil
 }
 }

+ 0 - 67
vendor/src/github.com/docker/notary/trustmanager/memorystore.go

@@ -1,67 +0,0 @@
-package trustmanager
-
-import (
-	"os"
-	"sync"
-)
-
-// MemoryFileStore is an implementation of Storage that keeps
-// the contents in memory.
-type MemoryFileStore struct {
-	sync.Mutex
-
-	files map[string][]byte
-}
-
-// NewMemoryFileStore creates a MemoryFileStore
-func NewMemoryFileStore() *MemoryFileStore {
-	return &MemoryFileStore{
-		files: make(map[string][]byte),
-	}
-}
-
-// Add writes data to a file with a given name
-func (f *MemoryFileStore) Add(name string, data []byte) error {
-	f.Lock()
-	defer f.Unlock()
-
-	f.files[name] = data
-	return nil
-}
-
-// Remove removes a file identified by name
-func (f *MemoryFileStore) Remove(name string) error {
-	f.Lock()
-	defer f.Unlock()
-
-	if _, present := f.files[name]; !present {
-		return os.ErrNotExist
-	}
-	delete(f.files, name)
-
-	return nil
-}
-
-// Get returns the data given a file name
-func (f *MemoryFileStore) Get(name string) ([]byte, error) {
-	f.Lock()
-	defer f.Unlock()
-
-	fileData, present := f.files[name]
-	if !present {
-		return nil, os.ErrNotExist
-	}
-
-	return fileData, nil
-}
-
-// ListFiles lists all the files inside of a store
-func (f *MemoryFileStore) ListFiles() []string {
-	var list []string
-
-	for name := range f.files {
-		list = append(list, name)
-	}
-
-	return list
-}

+ 0 - 42
vendor/src/github.com/docker/notary/trustmanager/store.go

@@ -1,42 +0,0 @@
-package trustmanager
-
-import (
-	"errors"
-
-	"github.com/docker/notary"
-)
-
-const (
-	visible = notary.PubCertPerms
-	private = notary.PrivKeyPerms
-)
-
-var (
-	// ErrPathOutsideStore indicates that the returned path would be
-	// outside the store
-	ErrPathOutsideStore = errors.New("path outside file store")
-)
-
-// Storage implements the bare bones primitives (no hierarchy)
-type Storage interface {
-	// Add writes a file to the specified location, returning an error if this
-	// is not possible (reasons may include permissions errors). The path is cleaned
-	// before being made absolute against the store's base dir.
-	Add(fileName string, data []byte) error
-
-	// Remove deletes a file from the store relative to the store's base directory.
-	// The path is cleaned before being made absolute to ensure no path traversal
-	// outside the base directory is possible.
-	Remove(fileName string) error
-
-	// Get returns the file content found at fileName relative to the base directory
-	// of the file store. The path is cleaned before being made absolute to ensure
-	// path traversal outside the store is not possible. If the file is not found
-	// an error to that effect is returned.
-	Get(fileName string) ([]byte, error)
-
-	// ListFiles returns a list of paths relative to the base directory of the
-	// filestore. Any of these paths must be retrievable via the
-	// Storage.Get method.
-	ListFiles() []string
-}

+ 57 - 0
vendor/src/github.com/docker/notary/trustmanager/yubikey/import.go

@@ -0,0 +1,57 @@
+// +build pkcs11
+
+package yubikey
+
+import (
+	"encoding/pem"
+	"errors"
+	"github.com/docker/notary"
+	"github.com/docker/notary/trustmanager"
+	"github.com/docker/notary/tuf/utils"
+)
+
+// YubiImport is a wrapper around the YubiStore that allows us to import private
+// keys to the yubikey
+type YubiImport struct {
+	dest          *YubiStore
+	passRetriever notary.PassRetriever
+}
+
+// NewImporter returns a wrapper for the YubiStore provided that enables importing
+// keys via the simple Set(string, []byte) interface
+func NewImporter(ys *YubiStore, ret notary.PassRetriever) *YubiImport {
+	return &YubiImport{
+		dest:          ys,
+		passRetriever: ret,
+	}
+}
+
+// Set determines if we are allowed to set the given key on the Yubikey and
+// calls through to YubiStore.AddKey if it's valid
+func (s *YubiImport) Set(name string, bytes []byte) error {
+	block, _ := pem.Decode(bytes)
+	if block == nil {
+		return errors.New("invalid PEM data, could not parse")
+	}
+	role, ok := block.Headers["role"]
+	if !ok {
+		return errors.New("no role found for key")
+	}
+	ki := trustmanager.KeyInfo{
+		// GUN is ignored by YubiStore
+		Role: role,
+	}
+	privKey, err := utils.ParsePEMPrivateKey(bytes, "")
+	if err != nil {
+		privKey, _, err = trustmanager.GetPasswdDecryptBytes(
+			s.passRetriever,
+			bytes,
+			name,
+			ki.Role,
+		)
+		if err != nil {
+			return err
+		}
+	}
+	return s.dest.AddKey(ki, privKey)
+}

+ 17 - 23
vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go

@@ -17,10 +17,11 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary/passphrase"
+	"github.com/docker/notary"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/signed"
+	"github.com/docker/notary/tuf/utils"
 	"github.com/miekg/pkcs11"
 	"github.com/miekg/pkcs11"
 )
 )
 
 
@@ -132,7 +133,7 @@ type yubiSlot struct {
 // YubiPrivateKey represents a private key inside of a yubikey
 // YubiPrivateKey represents a private key inside of a yubikey
 type YubiPrivateKey struct {
 type YubiPrivateKey struct {
 	data.ECDSAPublicKey
 	data.ECDSAPublicKey
-	passRetriever passphrase.Retriever
+	passRetriever notary.PassRetriever
 	slot          []byte
 	slot          []byte
 	libLoader     pkcs11LibLoader
 	libLoader     pkcs11LibLoader
 }
 }
@@ -143,9 +144,9 @@ type yubikeySigner struct {
 }
 }
 
 
 // NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
 // NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
-// interface except that the private material is inacessible
+// interface except that the private material is inaccessible
 func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
 func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
-	passRetriever passphrase.Retriever) *YubiPrivateKey {
+	passRetriever notary.PassRetriever) *YubiPrivateKey {
 
 
 	return &YubiPrivateKey{
 	return &YubiPrivateKey{
 		ECDSAPublicKey: pubKey,
 		ECDSAPublicKey: pubKey,
@@ -228,7 +229,7 @@ func addECDSAKey(
 	session pkcs11.SessionHandle,
 	session pkcs11.SessionHandle,
 	privKey data.PrivateKey,
 	privKey data.PrivateKey,
 	pkcs11KeyID []byte,
 	pkcs11KeyID []byte,
-	passRetriever passphrase.Retriever,
+	passRetriever notary.PassRetriever,
 	role string,
 	role string,
 ) error {
 ) error {
 	logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
 	logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
@@ -249,7 +250,7 @@ func addECDSAKey(
 
 
 	// Hard-coded policy: the generated certificate expires in 10 years.
 	// Hard-coded policy: the generated certificate expires in 10 years.
 	startTime := time.Now()
 	startTime := time.Now()
-	template, err := trustmanager.NewCertificate(role, startTime, startTime.AddDate(10, 0, 0))
+	template, err := utils.NewCertificate(role, startTime, startTime.AddDate(10, 0, 0))
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("failed to create the certificate template: %v", err)
 		return fmt.Errorf("failed to create the certificate template: %v", err)
 	}
 	}
@@ -345,7 +346,7 @@ func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byt
 }
 }
 
 
 // sign returns a signature for a given signature request
 // sign returns a signature for a given signature request
-func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) {
+func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, payload []byte) ([]byte, error) {
 	err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
 	err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
 	if err != nil {
 	if err != nil {
 		return nil, fmt.Errorf("error logging in: %v", err)
 		return nil, fmt.Errorf("error logging in: %v", err)
@@ -404,7 +405,7 @@ func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pass
 	return sig[:], nil
 	return sig[:], nil
 }
 }
 
 
-func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
+func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, keyID string) error {
 	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
 	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -615,7 +616,7 @@ func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, err
 
 
 // YubiStore is a KeyStore for private keys inside a Yubikey
 // YubiStore is a KeyStore for private keys inside a Yubikey
 type YubiStore struct {
 type YubiStore struct {
-	passRetriever passphrase.Retriever
+	passRetriever notary.PassRetriever
 	keys          map[string]yubiSlot
 	keys          map[string]yubiSlot
 	backupStore   trustmanager.KeyStore
 	backupStore   trustmanager.KeyStore
 	libLoader     pkcs11LibLoader
 	libLoader     pkcs11LibLoader
@@ -623,7 +624,7 @@ type YubiStore struct {
 
 
 // NewYubiStore returns a YubiStore, given a backup key store to write any
 // NewYubiStore returns a YubiStore, given a backup key store to write any
 // generated keys to (usually a KeyFileStore)
 // generated keys to (usually a KeyFileStore)
-func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
+func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever notary.PassRetriever) (
 	*YubiStore, error) {
 	*YubiStore, error) {
 
 
 	s := &YubiStore{
 	s := &YubiStore{
@@ -653,7 +654,7 @@ func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
 	}
 	}
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	if err != nil {
 	if err != nil {
-		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
+		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
 		return nil
 		return nil
 	}
 	}
 	defer cleanup(ctx, session)
 	defer cleanup(ctx, session)
@@ -697,7 +698,7 @@ func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) (
 
 
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	if err != nil {
 	if err != nil {
-		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
+		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
 		return false, err
 		return false, err
 	}
 	}
 	defer cleanup(ctx, session)
 	defer cleanup(ctx, session)
@@ -735,7 +736,7 @@ func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) (
 func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) {
 func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) {
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	if err != nil {
 	if err != nil {
-		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
+		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
 		if _, ok := err.(errHSMNotPresent); ok {
 		if _, ok := err.(errHSMNotPresent); ok {
 			err = trustmanager.ErrKeyNotFound{KeyID: keyID}
 			err = trustmanager.ErrKeyNotFound{KeyID: keyID}
 		}
 		}
@@ -770,7 +771,7 @@ func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) {
 func (s *YubiStore) RemoveKey(keyID string) error {
 func (s *YubiStore) RemoveKey(keyID string) error {
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
 	if err != nil {
 	if err != nil {
-		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
+		logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
 		return nil
 		return nil
 	}
 	}
 	defer cleanup(ctx, session)
 	defer cleanup(ctx, session)
@@ -789,12 +790,6 @@ func (s *YubiStore) RemoveKey(keyID string) error {
 	return err
 	return err
 }
 }
 
 
-// ExportKey doesn't work, because you can't export data from a Yubikey
-func (s *YubiStore) ExportKey(keyID string) ([]byte, error) {
-	logrus.Debugf("Attempting to export: %s key inside of YubiStore", keyID)
-	return nil, errors.New("Keys cannot be exported from a Yubikey.")
-}
-
 // GetKeyInfo is not yet implemented
 // GetKeyInfo is not yet implemented
 func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
 func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
 	return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
 	return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
@@ -874,7 +869,7 @@ func IsAccessible() bool {
 	return true
 	return true
 }
 }
 
 
-func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error {
+func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever notary.PassRetriever, userFlag uint, defaultPassw string) error {
 	// try default password
 	// try default password
 	err := ctx.Login(session, userFlag, defaultPassw)
 	err := ctx.Login(session, userFlag, defaultPassw)
 	if err == nil {
 	if err == nil {
@@ -902,13 +897,12 @@ func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever passphras
 			return trustmanager.ErrAttemptsExceeded{}
 			return trustmanager.ErrAttemptsExceeded{}
 		}
 		}
 
 
-		// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
+		// attempt to login. Loop if failed
 		err = ctx.Login(session, userFlag, passwd)
 		err = ctx.Login(session, userFlag, passwd)
 		if err == nil {
 		if err == nil {
 			return nil
 			return nil
 		}
 		}
 	}
 	}
-	return nil
 }
 }
 
 
 func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
 func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {

+ 37 - 0
vendor/src/github.com/docker/notary/trustpinning/ca.crt

@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+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
+lKmXlK4dTg==
+-----END CERTIFICATE-----
+

+ 60 - 37
vendor/src/github.com/docker/notary/trustpinning/certs.go

@@ -5,12 +5,11 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"strings"
 	"strings"
-	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/signed"
+	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
 // ErrValidationFail is returned when there is no valid trusted certificates
 // ErrValidationFail is returned when there is no valid trusted certificates
@@ -98,18 +97,25 @@ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trus
 	// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
 	// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
 	allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
 	allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
 	certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
 	certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun, true)
+	validIntCerts := validRootIntCerts(allIntCerts)
 
 
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
 		logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
 		return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
 		return nil, &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
 	}
 	}
 
 
+	logrus.Debugf("found %d leaf certs, of which %d are valid leaf certs for %s", len(allLeafCerts), len(certsFromRoot), gun)
+
 	// If we have a previous root, let's try to use it to validate that this new root is valid.
 	// If we have a previous root, let's try to use it to validate that this new root is valid.
-	if prevRoot != nil {
+	havePrevRoot := prevRoot != nil
+	if havePrevRoot {
 		// Retrieve all the trusted certificates from our previous root
 		// Retrieve all the trusted certificates from our previous root
 		// Note that we do not validate expiries here since our originally trusted root might have expired certs
 		// Note that we do not validate expiries here since our originally trusted root might have expired certs
 		allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
 		allTrustedLeafCerts, allTrustedIntCerts := parseAllCerts(prevRoot)
 		trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
 		trustedLeafCerts, err := validRootLeafCerts(allTrustedLeafCerts, gun, false)
+		if err != nil {
+			return nil, &ErrValidationFail{Reason: "could not retrieve trusted certs from previous root role data"}
+		}
 
 
 		// Use the certificates we found in the previous root for the GUN to verify its signatures
 		// Use the certificates we found in the previous root for the GUN to verify its signatures
 		// This could potentially be an empty set, in which case we will fail to verify
 		// This could potentially be an empty set, in which case we will fail to verify
@@ -121,45 +127,52 @@ func ValidateRoot(prevRoot *data.SignedRoot, root *data.Signed, gun string, trus
 		if !ok {
 		if !ok {
 			return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"}
 			return nil, &ErrValidationFail{Reason: "could not retrieve previous root role data"}
 		}
 		}
-
 		err = signed.VerifySignatures(
 		err = signed.VerifySignatures(
-			root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
+			root, data.BaseRole{Keys: utils.CertsToKeys(trustedLeafCerts, allTrustedIntCerts), Threshold: prevRootRoleData.Threshold})
 		if err != nil {
 		if err != nil {
 			logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
 			logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
 			return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"}
 			return nil, &ErrRootRotationFail{Reason: "failed to validate data with current trusted certificates"}
 		}
 		}
-	} else {
-		logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
-		trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
-		if err != nil {
-			return nil, &ErrValidationFail{Reason: err.Error()}
+		// Clear the IsValid marks we could have received from VerifySignatures
+		for i := range root.Signatures {
+			root.Signatures[i].IsValid = false
 		}
 		}
+	}
 
 
-		validPinnedCerts := map[string]*x509.Certificate{}
-		for id, cert := range certsFromRoot {
-			if ok := trustPinCheckFunc(cert, allIntCerts[id]); !ok {
-				continue
-			}
-			validPinnedCerts[id] = cert
-		}
-		if len(validPinnedCerts) == 0 {
-			return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
+	// Regardless of having a previous root or not, confirm that the new root validates against the trust pinning
+	logrus.Debugf("checking root against trust_pinning config", gun)
+	trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun, !havePrevRoot)
+	if err != nil {
+		return nil, &ErrValidationFail{Reason: err.Error()}
+	}
+
+	validPinnedCerts := map[string]*x509.Certificate{}
+	for id, cert := range certsFromRoot {
+		logrus.Debugf("checking trust-pinning for cert: %s", id)
+		if ok := trustPinCheckFunc(cert, validIntCerts[id]); !ok {
+			logrus.Debugf("trust-pinning check failed for cert: %s", id)
+			continue
 		}
 		}
-		certsFromRoot = validPinnedCerts
+		validPinnedCerts[id] = cert
 	}
 	}
+	if len(validPinnedCerts) == 0 {
+		return nil, &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
+	}
+	certsFromRoot = validPinnedCerts
 
 
 	// Validate the integrity of the new root (does it have valid signatures)
 	// Validate the integrity of the new root (does it have valid signatures)
 	// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
 	// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
 	// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
 	// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
 	err = signed.VerifySignatures(root, data.BaseRole{
 	err = signed.VerifySignatures(root, data.BaseRole{
-		Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
+		Keys: utils.CertsToKeys(certsFromRoot, validIntCerts), Threshold: rootRole.Threshold})
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
 		logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
 		return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
 		return nil, &ErrValidationFail{Reason: "failed to validate integrity of roots"}
 	}
 	}
 
 
-	logrus.Debugf("Root validation succeeded for %s", gun)
-	return signedRoot, nil
+	logrus.Debugf("root validation succeeded for %s", gun)
+	// Call RootFromSigned to make sure we pick up on the IsValid markings from VerifySignatures
+	return data.RootFromSigned(root)
 }
 }
 
 
 // validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
 // validRootLeafCerts returns a list of possibly (if checkExpiry is true) non-expired, non-sha1 certificates
@@ -177,17 +190,9 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, c
 			continue
 			continue
 		}
 		}
 		// Make sure the certificate is not expired if checkExpiry is true
 		// Make sure the certificate is not expired if checkExpiry is true
-		if checkExpiry && time.Now().After(cert.NotAfter) {
-			logrus.Debugf("error leaf certificate is expired")
-			continue
-		}
-
-		// We don't allow root certificates that use SHA1
-		if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
-			cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
-			cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
-
-			logrus.Debugf("error certificate uses deprecated hashing algorithm (SHA1)")
+		// and warn if it hasn't expired yet but is within 6 months of expiry
+		if err := utils.ValidateCertificate(cert, checkExpiry); err != nil {
+			logrus.Debugf("%s is invalid: %s", id, err.Error())
 			continue
 			continue
 		}
 		}
 
 
@@ -204,6 +209,24 @@ func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string, c
 	return validLeafCerts, nil
 	return validLeafCerts, nil
 }
 }
 
 
+// validRootIntCerts filters the passed in structure of intermediate certificates to only include non-expired, non-sha1 certificates
+// Note that this "validity" alone does not imply any measure of trust.
+func validRootIntCerts(allIntCerts map[string][]*x509.Certificate) map[string][]*x509.Certificate {
+	validIntCerts := make(map[string][]*x509.Certificate)
+
+	// Go through every leaf cert ID, and build its valid intermediate certificate list
+	for leafID, intCertList := range allIntCerts {
+		for _, intCert := range intCertList {
+			if err := utils.ValidateCertificate(intCert, true); err != nil {
+				continue
+			}
+			validIntCerts[leafID] = append(validIntCerts[leafID], intCert)
+		}
+
+	}
+	return validIntCerts
+}
+
 // parseAllCerts returns two maps, one with all of the leafCertificates and one
 // parseAllCerts returns two maps, one with all of the leafCertificates and one
 // with all the intermediate certificates found in signedRoot
 // with all the intermediate certificates found in signedRoot
 func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
 func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
@@ -233,14 +256,14 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
 
 
 		// Decode all the x509 certificates that were bundled with this
 		// Decode all the x509 certificates that were bundled with this
 		// Specific root key
 		// Specific root key
-		decodedCerts, err := trustmanager.LoadCertBundleFromPEM(key.Public())
+		decodedCerts, err := utils.LoadCertBundleFromPEM(key.Public())
 		if err != nil {
 		if err != nil {
 			logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
 			logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
 			continue
 			continue
 		}
 		}
 
 
 		// Get all non-CA certificates in the decoded certificates
 		// Get all non-CA certificates in the decoded certificates
-		leafCertList := trustmanager.GetLeafCerts(decodedCerts)
+		leafCertList := utils.GetLeafCerts(decodedCerts)
 
 
 		// If we got no leaf certificates or we got more than one, fail
 		// If we got no leaf certificates or we got more than one, fail
 		if len(leafCertList) != 1 {
 		if len(leafCertList) != 1 {
@@ -260,7 +283,7 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
 		leafCerts[key.ID()] = leafCert
 		leafCerts[key.ID()] = leafCert
 
 
 		// Get all the remainder certificates marked as a CA to be used as intermediates
 		// Get all the remainder certificates marked as a CA to be used as intermediates
-		intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
+		intermediateCerts := utils.GetIntermediateCerts(decodedCerts)
 		intCerts[key.ID()] = intermediateCerts
 		intCerts[key.ID()] = intermediateCerts
 	}
 	}
 
 

+ 31 - 0
vendor/src/github.com/docker/notary/trustpinning/test.crt

@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFKzCCAxWgAwIBAgIQRyp9QqcJfd3ayqdjiz8xIDALBgkqhkiG9w0BAQswODEa
+MBgGA1UEChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20v
+bm90YXJ5MB4XDTE1MDcxNzA2MzQyM1oXDTE3MDcxNjA2MzQyM1owODEaMBgGA1UE
+ChMRZG9ja2VyLmNvbS9ub3RhcnkxGjAYBgNVBAMTEWRvY2tlci5jb20vbm90YXJ5
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoQffrzsYnsH8vGf4Jh55
+Cj5wrjUGzD/sHkaFHptjJ6ToJGJv5yMAPxzyInu5sIoGLJapnYVBoAU0YgI9qlAc
+YA6SxaSwgm6rpvmnl8Qn0qc6ger3inpGaUJylWHuPwWkvcimQAqHZx2dQtL7g6kp
+rmKeTWpWoWLw3JoAUZUVhZMd6a22ZL/DvAw+Hrogbz4XeyahFb9IH402zPxN6vga
+JEFTF0Ji1jtNg0Mo4pb9SHsMsiw+LZK7SffHVKPxvd21m/biNmwsgExA3U8OOG8p
+uygfacys5c8+ZrX+ZFG/cvwKz0k6/QfJU40s6MhXw5C2WttdVmsG9/7rGFYjHoIJ
+weDyxgWk7vxKzRJI/un7cagDIaQsKrJQcCHIGFRlpIR5TwX7vl3R7cRncrDRMVvc
+VSEG2esxbw7jtzIp/ypnVRxcOny7IypyjKqVeqZ6HgxZtTBVrF1O/aHo2kvlwyRS
+Aus4kvh6z3+jzTm9EzfXiPQzY9BEk5gOLxhW9rc6UhlS+pe5lkaN/Hyqy/lPuq89
+fMr2rr7lf5WFdFnze6WNYMAaW7dNA4NE0dyD53428ZLXxNVPL4WU66Gac6lynQ8l
+r5tPsYIFXzh6FVaRKGQUtW1hz9ecO6Y27Rh2JsyiIxgUqk2ooxE69uN42t+dtqKC
+1s8G/7VtY8GDALFLYTnzLvsCAwEAAaM1MDMwDgYDVR0PAQH/BAQDAgCgMBMGA1Ud
+JQQMMAoGCCsGAQUFBwMDMAwGA1UdEwEB/wQCMAAwCwYJKoZIhvcNAQELA4ICAQBM
+Oll3G/XBz8idiNdNJDWUh+5w3ojmwanrTBdCdqEk1WenaR6DtcflJx6Z3f/mwV4o
+b1skOAX1yX5RCahJHUMxMicz/Q38pOVelGPrWnc3TJB+VKjGyHXlQDVkZFb+4+ef
+wtj7HngXhHFFDSgjm3EdMndvgDQ7SQb4skOnCNS9iyX7eXxhFBCZmZL+HALKBj2B
+yhV4IcBDqmp504t14rx9/Jvty0dG7fY7I51gEQpm4S02JML5xvTm1xfboWIhZODI
+swEAO+ekBoFHbS1Q9KMPjIAw3TrCHH8x8XZq5zsYtAC1yZHdCKa26aWdy56A9eHj
+O1VxzwmbNyXRenVuBYP+0wr3HVKFG4JJ4ZZpNZzQW/pqEPghCTJIvIueK652ByUc
+//sv+nXd5f19LeES9pf0l253NDaFZPb6aegKfquWh8qlQBmUQ2GzaTLbtmNd28M6
+W7iL7tkKZe1ZnBz9RKgtPrDjjWGZInjjcOU8EtT4SLq7kCVDmPs5MD8vaAm96JsE
+jmLC3Uu/4k7HiDYX0i0mOWkFjZQMdVatcIF5FPSppwsSbW8QidnXt54UtwtFDEPz
+lpjs7ybeQE71JXcMZnVIK4bjRXsEFPI98RpIlEdedbSUdYAncLNJRT7HZBMPGSwZ
+0PNJuglnlr3srVzdW1dz2xQjdvLwxy6mNUF6rbQBWA==
+-----END CERTIFICATE-----
+

+ 16 - 9
vendor/src/github.com/docker/notary/trustpinning/trustpin.go

@@ -4,7 +4,6 @@ import (
 	"crypto/x509"
 	"crypto/x509"
 	"fmt"
 	"fmt"
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/utils"
 	"github.com/docker/notary/tuf/utils"
 	"strings"
 	"strings"
 )
 )
@@ -28,25 +27,29 @@ type trustPinChecker struct {
 type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
 type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
 
 
 // NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
 // NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
-func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker, error) {
+func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string, firstBootstrap bool) (CertChecker, error) {
 	t := trustPinChecker{gun: gun, config: trustPinConfig}
 	t := trustPinChecker{gun: gun, config: trustPinConfig}
 	// Determine the mode, and if it's even valid
 	// Determine the mode, and if it's even valid
 	if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok {
 	if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok {
+		logrus.Debugf("trust-pinning using Cert IDs")
 		t.pinnedCertIDs = pinnedCerts
 		t.pinnedCertIDs = pinnedCerts
 		return t.certsCheck, nil
 		return t.certsCheck, nil
 	}
 	}
 
 
 	if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
 	if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
+		logrus.Debugf("trust-pinning using root CA bundle at: %s", caFilepath)
+
 		// Try to add the CA certs from its bundle file to our certificate store,
 		// Try to add the CA certs from its bundle file to our certificate store,
 		// and use it to validate certs in the root.json later
 		// and use it to validate certs in the root.json later
-		caCerts, err := trustmanager.LoadCertBundleFromFile(caFilepath)
+		caCerts, err := utils.LoadCertBundleFromFile(caFilepath)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("could not load root cert from CA path")
 			return nil, fmt.Errorf("could not load root cert from CA path")
 		}
 		}
 		// Now only consider certificates that are direct children from this CA cert chain
 		// Now only consider certificates that are direct children from this CA cert chain
 		caRootPool := x509.NewCertPool()
 		caRootPool := x509.NewCertPool()
 		for _, caCert := range caCerts {
 		for _, caCert := range caCerts {
-			if err = trustmanager.ValidateCertificate(caCert); err != nil {
+			if err = utils.ValidateCertificate(caCert, true); err != nil {
+				logrus.Debugf("ignoring root CA certificate with CN %s in bundle: %s", caCert.Subject.CommonName, err)
 				continue
 				continue
 			}
 			}
 			caRootPool.AddCert(caCert)
 			caRootPool.AddCert(caCert)
@@ -59,16 +62,18 @@ func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker,
 		return t.caCheck, nil
 		return t.caCheck, nil
 	}
 	}
 
 
-	if !trustPinConfig.DisableTOFU {
-		return t.tofusCheck, nil
+	// If TOFUs is disabled and we don't have any previous trusted root data for this GUN, we error out
+	if trustPinConfig.DisableTOFU && firstBootstrap {
+		return nil, fmt.Errorf("invalid trust pinning specified")
+
 	}
 	}
-	return nil, fmt.Errorf("invalid trust pinning specified")
+	return t.tofusCheck, nil
 }
 }
 
 
 func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
 func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
 	// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
 	// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
 	// in order to get the matching id in the root file
 	// in order to get the matching id in the root file
-	key, err := trustmanager.CertBundleToKey(leafCert, intCerts)
+	key, err := utils.CertBundleToKey(leafCert, intCerts)
 	if err != nil {
 	if err != nil {
 		logrus.Debug("error creating cert bundle: ", err.Error())
 		logrus.Debug("error creating cert bundle: ", err.Error())
 		return false
 		return false
@@ -84,9 +89,11 @@ func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Ce
 	}
 	}
 	// Attempt to find a valid certificate chain from the leaf cert to CA root
 	// Attempt to find a valid certificate chain from the leaf cert to CA root
 	// Use this certificate if such a valid chain exists (possibly using intermediates)
 	// Use this certificate if such a valid chain exists (possibly using intermediates)
-	if _, err := leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
+	var err error
+	if _, err = leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
 		return true
 		return true
 	}
 	}
+	logrus.Debugf("unable to find a valid certificate chain from leaf cert to CA root: %s", err)
 	return false
 	return false
 }
 }
 
 

+ 1 - 31
vendor/src/github.com/docker/notary/tuf/README.md

@@ -1,36 +1,6 @@
-# GOTUF 
-
-This is still a work in progress but will shortly be a fully compliant 
-Go implementation of [The Update Framework (TUF)](http://theupdateframework.com/).
-
-## Where's the CLI
-
-This repository provides a library only. The [Notary project](https://github.com/docker/notary)
-from Docker should be considered the official CLI to be used with this implementation of TUF.
-
-## TODOs:
-
-- [X] Add Targets to existing repo
-- [X] Sign metadata files
-- [X] Refactor TufRepo to take care of signing ~~and verification~~
-- [ ] Ensure consistent capitalization in naming (TUF\_\_\_ vs Tuf\_\_\_)
-- [X] Make caching of metadata files smarter - PR #5
-- [ ] ~~Add configuration for CLI commands. Order of configuration priority from most to least: flags, config file, defaults~~ Notary should be the official CLI
-- [X] Reasses organization of data types. Possibly consolidate a few things into the data package but break up package into a few more distinct files
-- [ ] Comprehensive test cases
-- [ ] Delete files no longer in use
-- [ ] Fix up errors. Some have to be instantiated, others don't, the inconsistency is annoying.
-- [X] Bump version numbers in meta files (could probably be done better)
-
 ## Credits
 ## Credits
 
 
-This implementation was originally forked from [flynn/go-tuf](https://github.com/flynn/go-tuf),
-however in attempting to add delegations I found I was making such
-significant changes that I could not maintain backwards compatibility
-without the code becoming overly convoluted.
-
-Some features such as pluggable verifiers have already been merged upstream to flynn/go-tuf
-and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations.
+This implementation was originally forked from [flynn/go-tuf](https://github.com/flynn/go-tuf)
 
 
 This implementation retains the same 3 Clause BSD license present on 
 This implementation retains the same 3 Clause BSD license present on 
 the original flynn implementation.
 the original flynn implementation.

+ 54 - 17
vendor/src/github.com/docker/notary/tuf/builder.go

@@ -18,7 +18,7 @@ var ErrBuildDone = fmt.Errorf(
 	"the builder has finished building and cannot accept any more input or produce any more output")
 	"the builder has finished building and cannot accept any more input or produce any more output")
 
 
 // ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
 // ErrInvalidBuilderInput is returned when RepoBuilder.Load is called
-// with the wrong type of metadata for thes tate that it's in
+// with the wrong type of metadata for the state that it's in
 type ErrInvalidBuilderInput struct{ msg string }
 type ErrInvalidBuilderInput struct{ msg string }
 
 
 func (e ErrInvalidBuilderInput) Error() string {
 func (e ErrInvalidBuilderInput) Error() string {
@@ -59,8 +59,9 @@ type RepoBuilder interface {
 	Load(roleName string, content []byte, minVersion int, allowExpired bool) error
 	Load(roleName string, content []byte, minVersion int, allowExpired bool) error
 	GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
 	GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int, error)
 	GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
 	GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error)
-	Finish() (*Repo, error)
+	Finish() (*Repo, *Repo, error)
 	BootstrapNewBuilder() RepoBuilder
 	BootstrapNewBuilder() RepoBuilder
+	BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder
 
 
 	// informative functions
 	// informative functions
 	IsLoaded(roleName string) bool
 	IsLoaded(roleName string) bool
@@ -80,8 +81,11 @@ func (f finishedBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, in
 func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
 func (f finishedBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, int, error) {
 	return nil, 0, ErrBuildDone
 	return nil, 0, ErrBuildDone
 }
 }
-func (f finishedBuilder) Finish() (*Repo, error)               { return nil, ErrBuildDone }
-func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder     { return f }
+func (f finishedBuilder) Finish() (*Repo, *Repo, error)    { return nil, nil, ErrBuildDone }
+func (f finishedBuilder) BootstrapNewBuilder() RepoBuilder { return f }
+func (f finishedBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
+	return f
+}
 func (f finishedBuilder) IsLoaded(roleName string) bool        { return false }
 func (f finishedBuilder) IsLoaded(roleName string) bool        { return false }
 func (f finishedBuilder) GetLoadedVersion(roleName string) int { return 0 }
 func (f finishedBuilder) GetLoadedVersion(roleName string) int { return 0 }
 func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
 func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
@@ -90,12 +94,21 @@ func (f finishedBuilder) GetConsistentInfo(roleName string) ConsistentInfo {
 
 
 // NewRepoBuilder is the only way to get a pre-built RepoBuilder
 // NewRepoBuilder is the only way to get a pre-built RepoBuilder
 func NewRepoBuilder(gun string, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
 func NewRepoBuilder(gun string, cs signed.CryptoService, trustpin trustpinning.TrustPinConfig) RepoBuilder {
-	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
-		repo:                 NewRepo(cs),
-		gun:                  gun,
-		trustpin:             trustpin,
-		loadedNotChecksummed: make(map[string][]byte),
-	}}
+	return NewBuilderFromRepo(gun, NewRepo(cs), trustpin)
+}
+
+// NewBuilderFromRepo allows us to bootstrap a builder given existing repo data.
+// YOU PROBABLY SHOULDN'T BE USING THIS OUTSIDE OF TESTING CODE!!!
+func NewBuilderFromRepo(gun string, repo *Repo, trustpin trustpinning.TrustPinConfig) RepoBuilder {
+	return &repoBuilderWrapper{
+		RepoBuilder: &repoBuilder{
+			repo:                 repo,
+			invalidRoles:         NewRepo(nil),
+			gun:                  gun,
+			trustpin:             trustpin,
+			loadedNotChecksummed: make(map[string][]byte),
+		},
+	}
 }
 }
 
 
 // repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
 // repoBuilderWrapper embeds a repoBuilder, but once Finish is called, swaps
@@ -104,7 +117,7 @@ type repoBuilderWrapper struct {
 	RepoBuilder
 	RepoBuilder
 }
 }
 
 
-func (rbw *repoBuilderWrapper) Finish() (*Repo, error) {
+func (rbw *repoBuilderWrapper) Finish() (*Repo, *Repo, error) {
 	switch rbw.RepoBuilder.(type) {
 	switch rbw.RepoBuilder.(type) {
 	case finishedBuilder:
 	case finishedBuilder:
 		return rbw.RepoBuilder.Finish()
 		return rbw.RepoBuilder.Finish()
@@ -117,7 +130,8 @@ func (rbw *repoBuilderWrapper) Finish() (*Repo, error) {
 
 
 // repoBuilder actually builds a tuf.Repo
 // repoBuilder actually builds a tuf.Repo
 type repoBuilder struct {
 type repoBuilder struct {
-	repo *Repo
+	repo         *Repo
+	invalidRoles *Repo
 
 
 	// needed for root trust pininng verification
 	// needed for root trust pininng verification
 	gun      string
 	gun      string
@@ -136,13 +150,14 @@ type repoBuilder struct {
 	nextRootChecksum *data.FileMeta
 	nextRootChecksum *data.FileMeta
 }
 }
 
 
-func (rb *repoBuilder) Finish() (*Repo, error) {
-	return rb.repo, nil
+func (rb *repoBuilder) Finish() (*Repo, *Repo, error) {
+	return rb.repo, rb.invalidRoles, nil
 }
 }
 
 
 func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
 func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
 	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
 	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
 		repo:                 NewRepo(rb.repo.cryptoService),
 		repo:                 NewRepo(rb.repo.cryptoService),
+		invalidRoles:         NewRepo(nil),
 		gun:                  rb.gun,
 		gun:                  rb.gun,
 		loadedNotChecksummed: make(map[string][]byte),
 		loadedNotChecksummed: make(map[string][]byte),
 		trustpin:             rb.trustpin,
 		trustpin:             rb.trustpin,
@@ -152,6 +167,18 @@ func (rb *repoBuilder) BootstrapNewBuilder() RepoBuilder {
 	}}
 	}}
 }
 }
 
 
+func (rb *repoBuilder) BootstrapNewBuilderWithNewTrustpin(trustpin trustpinning.TrustPinConfig) RepoBuilder {
+	return &repoBuilderWrapper{RepoBuilder: &repoBuilder{
+		repo:                 NewRepo(rb.repo.cryptoService),
+		gun:                  rb.gun,
+		loadedNotChecksummed: make(map[string][]byte),
+		trustpin:             trustpin,
+
+		prevRoot:                 rb.repo.Root,
+		bootstrappedRootChecksum: rb.nextRootChecksum,
+	}}
+}
+
 // IsLoaded returns whether a particular role has already been loaded
 // IsLoaded returns whether a particular role has already been loaded
 func (rb *repoBuilder) IsLoaded(roleName string) bool {
 func (rb *repoBuilder) IsLoaded(roleName string) bool {
 	switch roleName {
 	switch roleName {
@@ -338,7 +365,7 @@ func (rb *repoBuilder) GenerateTimestamp(prev *data.SignedTimestamp) ([]byte, in
 		return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
 		return nil, 0, ErrInvalidBuilderInput{msg: "timestamp has already been loaded"}
 	}
 	}
 
 
-	// SignTimetamp always serializes the loaded snapshot and signs in the data, so we must always
+	// SignTimestamp always serializes the loaded snapshot and signs in the data, so we must always
 	// have the snapshot loaded first
 	// have the snapshot loaded first
 	if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
 	if err := rb.checkPrereqsLoaded([]string{data.CanonicalRootRole, data.CanonicalSnapshotRole}); err != nil {
 		return nil, 0, err
 		return nil, 0, err
@@ -411,7 +438,6 @@ func (rb *repoBuilder) loadRoot(content []byte, minVersion int, allowExpired boo
 	if err != nil { // this should never happen since the root has been validated
 	if err != nil { // this should never happen since the root has been validated
 		return err
 		return err
 	}
 	}
-
 	rb.repo.Root = signedRoot
 	rb.repo.Root = signedRoot
 	rb.repo.originalRootRole = rootRole
 	rb.repo.originalRootRole = rootRole
 	return nil
 	return nil
@@ -524,6 +550,7 @@ func (rb *repoBuilder) loadTargets(content []byte, minVersion int, allowExpired
 		}
 		}
 	}
 	}
 
 
+	signedTargets.Signatures = signedObj.Signatures
 	rb.repo.Targets[roleName] = signedTargets
 	rb.repo.Targets[roleName] = signedTargets
 	return nil
 	return nil
 }
 }
@@ -534,7 +561,8 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio
 		return err
 		return err
 	}
 	}
 
 
-	signedObj, err := rb.bytesToSignedAndValidateSigs(delegationRole.BaseRole, content)
+	// bytesToSigned checks checksum
+	signedObj, err := rb.bytesToSigned(content, roleName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -545,15 +573,24 @@ func (rb *repoBuilder) loadDelegation(roleName string, content []byte, minVersio
 	}
 	}
 
 
 	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
 	if err := signed.VerifyVersion(&(signedTargets.Signed.SignedCommon), minVersion); err != nil {
+		// don't capture in invalidRoles because the role we received is a rollback
+		return err
+	}
+
+	// verify signature
+	if err := signed.VerifySignatures(signedObj, delegationRole.BaseRole); err != nil {
+		rb.invalidRoles.Targets[roleName] = signedTargets
 		return err
 		return err
 	}
 	}
 
 
 	if !allowExpired { // check must go at the end because all other validation should pass
 	if !allowExpired { // check must go at the end because all other validation should pass
 		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
 		if err := signed.VerifyExpiry(&(signedTargets.Signed.SignedCommon), roleName); err != nil {
+			rb.invalidRoles.Targets[roleName] = signedTargets
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
+	signedTargets.Signatures = signedObj.Signatures
 	rb.repo.Targets[roleName] = signedTargets
 	rb.repo.Targets[roleName] = signedTargets
 	return nil
 	return nil
 }
 }

+ 0 - 14
vendor/src/github.com/docker/notary/tuf/client/errors.go

@@ -1,14 +0,0 @@
-package client
-
-import (
-	"fmt"
-)
-
-// ErrCorruptedCache - local data is incorrect
-type ErrCorruptedCache struct {
-	file string
-}
-
-func (e ErrCorruptedCache) Error() string {
-	return fmt.Sprintf("cache is corrupted: %s", e.file)
-}

+ 9 - 0
vendor/src/github.com/docker/notary/tuf/data/errors.go

@@ -42,3 +42,12 @@ func (e ErrMismatchedChecksum) Error() string {
 	return fmt.Sprintf("%s checksum for %s did not match: expected %s", e.alg, e.name,
 	return fmt.Sprintf("%s checksum for %s did not match: expected %s", e.alg, e.name,
 		e.expected)
 		e.expected)
 }
 }
+
+// ErrCertExpired is the error to be returned when a certificate has expired
+type ErrCertExpired struct {
+	CN string
+}
+
+func (e ErrCertExpired) Error() string {
+	return fmt.Sprintf("certificate with CN %s is expired", e.CN)
+}

+ 25 - 0
vendor/src/github.com/docker/notary/tuf/data/roles.go

@@ -86,6 +86,31 @@ func IsDelegation(role string) bool {
 		isClean
 		isClean
 }
 }
 
 
+// IsBaseRole checks if the role is a base role
+func IsBaseRole(role string) bool {
+	for _, baseRole := range BaseRoles {
+		if role == baseRole {
+			return true
+		}
+	}
+	return false
+}
+
+// IsWildDelegation determines if a role represents a valid wildcard delegation
+// path, i.e. targets/*, targets/foo/*.
+// The wildcard may only appear as the final part of the delegation and must
+// be a whole segment, i.e. targets/foo* is not a valid wildcard delegation.
+func IsWildDelegation(role string) bool {
+	if path.Clean(role) != role {
+		return false
+	}
+	base := path.Dir(role)
+	if !(IsDelegation(base) || base == CanonicalTargetsRole) {
+		return false
+	}
+	return role[len(role)-2:] == "/*"
+}
+
 // BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
 // BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
 type BaseRole struct {
 type BaseRole struct {
 	Keys      map[string]PublicKey
 	Keys      map[string]PublicKey

+ 4 - 1
vendor/src/github.com/docker/notary/tuf/data/targets.go

@@ -107,7 +107,10 @@ func (t *SignedTargets) BuildDelegationRole(roleName string) (DelegationRole, er
 				pubKey, ok := t.Signed.Delegations.Keys[keyID]
 				pubKey, ok := t.Signed.Delegations.Keys[keyID]
 				if !ok {
 				if !ok {
 					// Couldn't retrieve all keys, so stop walking and return invalid role
 					// Couldn't retrieve all keys, so stop walking and return invalid role
-					return DelegationRole{}, ErrInvalidRole{Role: roleName, Reason: "delegation does not exist with all specified keys"}
+					return DelegationRole{}, ErrInvalidRole{
+						Role:   roleName,
+						Reason: "role lists unknown key " + keyID + " as a signing key",
+					}
 				}
 				}
 				pubKeys[keyID] = pubKey
 				pubKeys[keyID] = pubKey
 			}
 			}

+ 35 - 0
vendor/src/github.com/docker/notary/tuf/data/types.go

@@ -111,6 +111,7 @@ type Signature struct {
 	KeyID     string       `json:"keyid"`
 	KeyID     string       `json:"keyid"`
 	Method    SigAlgorithm `json:"method"`
 	Method    SigAlgorithm `json:"method"`
 	Signature []byte       `json:"sig"`
 	Signature []byte       `json:"sig"`
+	IsValid   bool         `json:"-"`
 }
 }
 
 
 // Files is the map of paths to file meta container in targets and delegations
 // Files is the map of paths to file meta container in targets and delegations
@@ -161,6 +162,40 @@ func CheckHashes(payload []byte, name string, hashes Hashes) error {
 	return nil
 	return nil
 }
 }
 
 
+// CompareMultiHashes verifies that the two Hashes passed in can represent the same data.
+// This means that both maps must have at least one key defined for which they map, and no conflicts.
+// Note that we check the intersection of map keys, which adds support for non-default hash algorithms in notary
+func CompareMultiHashes(hashes1, hashes2 Hashes) error {
+	// First check if the two hash structures are valid
+	if err := CheckValidHashStructures(hashes1); err != nil {
+		return err
+	}
+	if err := CheckValidHashStructures(hashes2); err != nil {
+		return err
+	}
+	// Check if they have at least one matching hash, and no conflicts
+	cnt := 0
+	for hashAlg, hash1 := range hashes1 {
+
+		hash2, ok := hashes2[hashAlg]
+		if !ok {
+			continue
+		}
+
+		if subtle.ConstantTimeCompare(hash1[:], hash2[:]) == 0 {
+			return fmt.Errorf("mismatched %s checksum", hashAlg)
+		}
+		// If we reached here, we had a match
+		cnt++
+	}
+
+	if cnt == 0 {
+		return fmt.Errorf("at least one matching hash needed")
+	}
+
+	return nil
+}
+
 // CheckValidHashStructures returns an error, or nil, depending on whether
 // CheckValidHashStructures returns an error, or nil, depending on whether
 // the content of the hashes is valid or not.
 // the content of the hashes is valid or not.
 func CheckValidHashStructures(hashes Hashes) error {
 func CheckValidHashStructures(hashes Hashes) error {

+ 6 - 2
vendor/src/github.com/docker/notary/tuf/signed/ed25519.go

@@ -6,6 +6,7 @@ import (
 
 
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/utils"
 )
 )
 
 
 type edCryptoKey struct {
 type edCryptoKey struct {
@@ -72,7 +73,7 @@ func (e *Ed25519) Create(role, gun, algorithm string) (data.PublicKey, error) {
 		return nil, errors.New("only ED25519 supported by this cryptoservice")
 		return nil, errors.New("only ED25519 supported by this cryptoservice")
 	}
 	}
 
 
-	private, err := trustmanager.GenerateED25519Key(rand.Reader)
+	private, err := utils.GenerateED25519Key(rand.Reader)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -95,7 +96,10 @@ func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error
 
 
 // GetKey returns a single public key based on the ID
 // GetKey returns a single public key based on the ID
 func (e *Ed25519) GetKey(keyID string) data.PublicKey {
 func (e *Ed25519) GetKey(keyID string) data.PublicKey {
-	return data.PublicKeyFromPrivate(e.keys[keyID].privKey)
+	if privKey, _, err := e.GetPrivateKey(keyID); err == nil {
+		return data.PublicKeyFromPrivate(privKey)
+	}
+	return nil
 }
 }
 
 
 // GetPrivateKey returns a single private key and role if present, based on the ID
 // GetPrivateKey returns a single private key and role if present, based on the ID

+ 9 - 4
vendor/src/github.com/docker/notary/tuf/signed/errors.go

@@ -14,12 +14,17 @@ type ErrInsufficientSignatures struct {
 }
 }
 
 
 func (e ErrInsufficientSignatures) Error() string {
 func (e ErrInsufficientSignatures) Error() string {
-	candidates := strings.Join(e.MissingKeyIDs, ", ")
+	candidates := ""
+	if len(e.MissingKeyIDs) > 0 {
+		candidates = fmt.Sprintf(" (%s)", strings.Join(e.MissingKeyIDs, ", "))
+	}
+
 	if e.FoundKeys == 0 {
 	if e.FoundKeys == 0 {
-		return fmt.Sprintf("signing keys not available, need %d keys out of: %s", e.NeededKeys, candidates)
+		return fmt.Sprintf("signing keys not available: need %d keys from %d possible keys%s",
+			e.NeededKeys, len(e.MissingKeyIDs), candidates)
 	}
 	}
-	return fmt.Sprintf("not enough signing keys: got %d of %d needed keys, other candidates: %s",
-		e.FoundKeys, e.NeededKeys, candidates)
+	return fmt.Sprintf("not enough signing keys: found %d of %d needed keys - %d other possible keys%s",
+		e.FoundKeys, e.NeededKeys, len(e.MissingKeyIDs), candidates)
 }
 }
 
 
 // ErrExpired indicates a piece of metadata has expired
 // ErrExpired indicates a piece of metadata has expired

+ 1 - 1
vendor/src/github.com/docker/notary/tuf/signed/sign.go

@@ -100,7 +100,7 @@ func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey,
 			// key is no longer a valid signing key
 			// key is no longer a valid signing key
 			continue
 			continue
 		}
 		}
-		if err := VerifySignature(*s.Signed, sig, k); err != nil {
+		if err := VerifySignature(*s.Signed, &sig, k); err != nil {
 			// signature is no longer valid
 			// signature is no longer valid
 			continue
 			continue
 		}
 		}

+ 9 - 4
vendor/src/github.com/docker/notary/tuf/signed/verify.go

@@ -66,7 +66,8 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
 	}
 	}
 
 
 	valid := make(map[string]struct{})
 	valid := make(map[string]struct{})
-	for _, sig := range s.Signatures {
+	for i := range s.Signatures {
+		sig := &(s.Signatures[i])
 		logrus.Debug("verifying signature for key ID: ", sig.KeyID)
 		logrus.Debug("verifying signature for key ID: ", sig.KeyID)
 		key, ok := roleData.Keys[sig.KeyID]
 		key, ok := roleData.Keys[sig.KeyID]
 		if !ok {
 		if !ok {
@@ -82,17 +83,20 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
 			continue
 			continue
 		}
 		}
 		valid[sig.KeyID] = struct{}{}
 		valid[sig.KeyID] = struct{}{}
-
 	}
 	}
 	if len(valid) < roleData.Threshold {
 	if len(valid) < roleData.Threshold {
-		return ErrRoleThreshold{}
+		return ErrRoleThreshold{
+			Msg: fmt.Sprintf("valid signatures did not meet threshold for %s", roleData.Name),
+		}
 	}
 	}
 
 
 	return nil
 	return nil
 }
 }
 
 
 // VerifySignature checks a single signature and public key against a payload
 // VerifySignature checks a single signature and public key against a payload
-func VerifySignature(msg []byte, sig data.Signature, pk data.PublicKey) error {
+// If the signature is verified, the signature's is valid field will actually
+// be mutated to be equal to the boolean true
+func VerifySignature(msg []byte, sig *data.Signature, pk data.PublicKey) error {
 	// method lookup is consistent due to Unmarshal JSON doing lower case for us.
 	// method lookup is consistent due to Unmarshal JSON doing lower case for us.
 	method := sig.Method
 	method := sig.Method
 	verifier, ok := Verifiers[method]
 	verifier, ok := Verifiers[method]
@@ -103,5 +107,6 @@ func VerifySignature(msg []byte, sig data.Signature, pk data.PublicKey) error {
 	if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
 	if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
 		return fmt.Errorf("signature was invalid\n")
 		return fmt.Errorf("signature was invalid\n")
 	}
 	}
+	sig.IsValid = true
 	return nil
 	return nil
 }
 }

+ 0 - 102
vendor/src/github.com/docker/notary/tuf/store/filestore.go

@@ -1,102 +0,0 @@
-package store
-
-import (
-	"fmt"
-	"github.com/docker/notary"
-	"io/ioutil"
-	"os"
-	"path"
-	"path/filepath"
-)
-
-// NewFilesystemStore creates a new store in a directory tree
-func NewFilesystemStore(baseDir, metaSubDir, metaExtension string) (*FilesystemStore, error) {
-	metaDir := path.Join(baseDir, metaSubDir)
-
-	// Make sure we can create the necessary dirs and they are writable
-	err := os.MkdirAll(metaDir, 0700)
-	if err != nil {
-		return nil, err
-	}
-
-	return &FilesystemStore{
-		baseDir:       baseDir,
-		metaDir:       metaDir,
-		metaExtension: metaExtension,
-	}, nil
-}
-
-// FilesystemStore is a store in a locally accessible directory
-type FilesystemStore struct {
-	baseDir       string
-	metaDir       string
-	metaExtension string
-}
-
-func (f *FilesystemStore) getPath(name string) string {
-	fileName := fmt.Sprintf("%s.%s", name, f.metaExtension)
-	return filepath.Join(f.metaDir, fileName)
-}
-
-// GetMeta returns the meta for the given name (a role) up to size bytes
-// If size is "NoSizeLimit", this corresponds to "infinite," but we cut off at a
-// predefined threshold "notary.MaxDownloadSize".
-func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
-	meta, err := ioutil.ReadFile(f.getPath(name))
-	if err != nil {
-		if os.IsNotExist(err) {
-			err = ErrMetaNotFound{Resource: name}
-		}
-		return nil, err
-	}
-	if size == NoSizeLimit {
-		size = notary.MaxDownloadSize
-	}
-	// Only return up to size bytes
-	if int64(len(meta)) < size {
-		return meta, nil
-	}
-	return meta[:size], nil
-}
-
-// SetMultiMeta sets the metadata for multiple roles in one operation
-func (f *FilesystemStore) SetMultiMeta(metas map[string][]byte) error {
-	for role, blob := range metas {
-		err := f.SetMeta(role, blob)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// SetMeta sets the meta for a single role
-func (f *FilesystemStore) SetMeta(name string, meta []byte) error {
-	fp := f.getPath(name)
-
-	// Ensures the parent directories of the file we are about to write exist
-	err := os.MkdirAll(filepath.Dir(fp), 0700)
-	if err != nil {
-		return err
-	}
-
-	// if something already exists, just delete it and re-write it
-	os.RemoveAll(fp)
-
-	// Write the file to disk
-	if err = ioutil.WriteFile(fp, meta, 0600); err != nil {
-		return err
-	}
-	return nil
-}
-
-// RemoveAll clears the existing filestore by removing its base directory
-func (f *FilesystemStore) RemoveAll() error {
-	return os.RemoveAll(f.baseDir)
-}
-
-// RemoveMeta removes the metadata for a single role - if the metadata doesn't
-// exist, no error is returned
-func (f *FilesystemStore) RemoveMeta(name string) error {
-	return os.RemoveAll(f.getPath(name)) // RemoveAll succeeds if path doesn't exist
-}

+ 97 - 18
vendor/src/github.com/docker/notary/tuf/tuf.go

@@ -77,11 +77,10 @@ type Repo struct {
 // If the Repo will only be used for reading, the CryptoService
 // If the Repo will only be used for reading, the CryptoService
 // can be nil.
 // can be nil.
 func NewRepo(cryptoService signed.CryptoService) *Repo {
 func NewRepo(cryptoService signed.CryptoService) *Repo {
-	repo := &Repo{
+	return &Repo{
 		Targets:       make(map[string]*data.SignedTargets),
 		Targets:       make(map[string]*data.SignedTargets),
 		cryptoService: cryptoService,
 		cryptoService: cryptoService,
 	}
 	}
-	return repo
 }
 }
 
 
 // AddBaseKeys is used to add keys to the role in root.json
 // AddBaseKeys is used to add keys to the role in root.json
@@ -245,6 +244,21 @@ func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) {
 				if err != nil {
 				if err != nil {
 					return err
 					return err
 				}
 				}
+				// Check all public key certificates in the role for expiry
+				// Currently we do not reject expired delegation keys but warn if they might expire soon or have already
+				for keyID, pubKey := range delgRole.Keys {
+					certFromKey, err := utils.LoadCertFromPEM(pubKey.Public())
+					if err != nil {
+						continue
+					}
+					if err := utils.ValidateCertificate(certFromKey, true); err != nil {
+						if _, ok := err.(data.ErrCertExpired); !ok {
+							// do not allow other invalid cert errors
+							return err
+						}
+						logrus.Warnf("error with delegation %s key ID %d: %s", delgRole.Name, keyID, err)
+					}
+				}
 				foundRole = &delgRole
 				foundRole = &delgRole
 				return StopWalk{}
 				return StopWalk{}
 			}
 			}
@@ -325,17 +339,16 @@ func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys,
 				break
 				break
 			}
 			}
 		}
 		}
-		// We didn't find the role earlier, so create it only if we have keys to add
+		// We didn't find the role earlier, so create it.
+		if addKeys == nil {
+			addKeys = data.KeyList{} // initialize to empty list if necessary so calling .IDs() below won't panic
+		}
 		if delgRole == nil {
 		if delgRole == nil {
-			if len(addKeys) > 0 {
-				delgRole, err = data.NewRole(roleName, newThreshold, addKeys.IDs(), addPaths)
-				if err != nil {
-					return err
-				}
-			} else {
-				// If we can't find the role and didn't specify keys to add, this is an error
-				return data.ErrInvalidRole{Role: roleName, Reason: "cannot create new delegation without keys"}
+			delgRole, err = data.NewRole(roleName, newThreshold, addKeys.IDs(), addPaths)
+			if err != nil {
+				return err
 			}
 			}
+
 		}
 		}
 		// Add the key IDs to the role and the keys themselves to the parent
 		// Add the key IDs to the role and the keys themselves to the parent
 		for _, k := range addKeys {
 		for _, k := range addKeys {
@@ -345,7 +358,7 @@ func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys,
 		}
 		}
 		// Make sure we have a valid role still
 		// Make sure we have a valid role still
 		if len(delgRole.KeyIDs) < delgRole.Threshold {
 		if len(delgRole.KeyIDs) < delgRole.Threshold {
-			return data.ErrInvalidRole{Role: roleName, Reason: "insufficient keys to meet threshold"}
+			logrus.Warnf("role %s has fewer keys than its threshold of %d; it will not be usable until keys are added to it", delgRole.Name, delgRole.Threshold)
 		}
 		}
 		// NOTE: this closure CANNOT error after this point, as we've committed to editing the SignedTargets metadata in the repo object.
 		// NOTE: this closure CANNOT error after this point, as we've committed to editing the SignedTargets metadata in the repo object.
 		// Any errors related to updating this delegation must occur before this point.
 		// Any errors related to updating this delegation must occur before this point.
@@ -392,11 +405,77 @@ func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, remo
 	// Walk to the parent of this delegation, since that is where its role metadata exists
 	// Walk to the parent of this delegation, since that is where its role metadata exists
 	// We do not have to verify that the walker reached its desired role in this scenario
 	// We do not have to verify that the walker reached its desired role in this scenario
 	// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
 	// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
-	err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
-	if err != nil {
-		return err
+	return tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
+}
+
+// PurgeDelegationKeys removes the provided canonical key IDs from all delegations
+// present in the subtree rooted at role. The role argument must be provided in a wildcard
+// format, i.e. targets/* would remove the key from all delegations in the repo
+func (tr *Repo) PurgeDelegationKeys(role string, removeKeys []string) error {
+	if !data.IsWildDelegation(role) {
+		return data.ErrInvalidRole{
+			Role:   role,
+			Reason: "only wildcard roles can be used in a purge",
+		}
 	}
 	}
-	return nil
+
+	removeIDs := make(map[string]struct{})
+	for _, id := range removeKeys {
+		removeIDs[id] = struct{}{}
+	}
+
+	start := path.Dir(role)
+	tufIDToCanon := make(map[string]string)
+
+	purgeKeys := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
+		var (
+			deleteCandidates []string
+			err              error
+		)
+		for id, key := range tgt.Signed.Delegations.Keys {
+			var (
+				canonID string
+				ok      bool
+			)
+			if canonID, ok = tufIDToCanon[id]; !ok {
+				canonID, err = utils.CanonicalKeyID(key)
+				if err != nil {
+					return err
+				}
+				tufIDToCanon[id] = canonID
+			}
+			if _, ok := removeIDs[canonID]; ok {
+				deleteCandidates = append(deleteCandidates, id)
+			}
+		}
+		if len(deleteCandidates) == 0 {
+			// none of the interesting keys were present. We're done with this role
+			return nil
+		}
+		// now we know there are changes, check if we'll be able to sign them in
+		if err := tr.VerifyCanSign(validRole.Name); err != nil {
+			logrus.Warnf(
+				"role %s contains keys being purged but you do not have the necessary keys present to sign it; keys will not be purged from %s or its immediate children",
+				validRole.Name,
+				validRole.Name,
+			)
+			return nil
+		}
+		// we know we can sign in the changes, delete the keys
+		for _, id := range deleteCandidates {
+			delete(tgt.Signed.Delegations.Keys, id)
+		}
+		// delete candidate keys from all roles.
+		for _, role := range tgt.Signed.Delegations.Roles {
+			role.RemoveKeys(deleteCandidates)
+			if len(role.KeyIDs) < role.Threshold {
+				logrus.Warnf("role %s has fewer keys than its threshold of %d; it will not be usable until keys are added to it", role.Name, role.Threshold)
+			}
+		}
+		tgt.Dirty = true
+		return nil
+	}
+	return tr.WalkTargets("", start, purgeKeys)
 }
 }
 
 
 // UpdateDelegationPaths updates the appropriate delegation's paths.
 // UpdateDelegationPaths updates the appropriate delegation's paths.
@@ -655,7 +734,7 @@ func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisito
 		}
 		}
 
 
 		// Determine whether to visit this role or not:
 		// Determine whether to visit this role or not:
-		// If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree
+		// If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree.
 		// Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority)
 		// Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority)
 		if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) {
 		if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) {
 			// If we had matching path or role name, visit this target and determine whether or not to keep walking
 			// If we had matching path or role name, visit this target and determine whether or not to keep walking
@@ -948,7 +1027,7 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error
 	if _, ok := tr.Targets[role]; !ok {
 	if _, ok := tr.Targets[role]; !ok {
 		return nil, data.ErrInvalidRole{
 		return nil, data.ErrInvalidRole{
 			Role:   role,
 			Role:   role,
-			Reason: "SignTargets called with non-existant targets role",
+			Reason: "SignTargets called with non-existent targets role",
 		}
 		}
 	}
 	}
 	tr.Targets[role].Signed.Expires = expires
 	tr.Targets[role].Signed.Expires = expires

+ 0 - 109
vendor/src/github.com/docker/notary/tuf/utils/util.go

@@ -1,109 +0,0 @@
-package utils
-
-import (
-	"crypto/hmac"
-	"encoding/hex"
-	"errors"
-	"fmt"
-	gopath "path"
-	"path/filepath"
-
-	"github.com/docker/notary/trustmanager"
-	"github.com/docker/notary/tuf/data"
-)
-
-// ErrWrongLength indicates the length was different to that expected
-var ErrWrongLength = errors.New("wrong length")
-
-// ErrWrongHash indicates the hash was different to that expected
-type ErrWrongHash struct {
-	Type     string
-	Expected []byte
-	Actual   []byte
-}
-
-// Error implements error interface
-func (e ErrWrongHash) Error() string {
-	return fmt.Sprintf("wrong %s hash, expected %#x got %#x", e.Type, e.Expected, e.Actual)
-}
-
-// ErrNoCommonHash indicates the metadata did not provide any hashes this
-// client recognizes
-type ErrNoCommonHash struct {
-	Expected data.Hashes
-	Actual   data.Hashes
-}
-
-// Error implements error interface
-func (e ErrNoCommonHash) Error() string {
-	types := func(a data.Hashes) []string {
-		t := make([]string, 0, len(a))
-		for typ := range a {
-			t = append(t, typ)
-		}
-		return t
-	}
-	return fmt.Sprintf("no common hash function, expected one of %s, got %s", types(e.Expected), types(e.Actual))
-}
-
-// ErrUnknownHashAlgorithm - client was ashed to use a hash algorithm
-// it is not familiar with
-type ErrUnknownHashAlgorithm struct {
-	Name string
-}
-
-// Error implements error interface
-func (e ErrUnknownHashAlgorithm) Error() string {
-	return fmt.Sprintf("unknown hash algorithm: %s", e.Name)
-}
-
-// PassphraseFunc type for func that request a passphrase
-type PassphraseFunc func(role string, confirm bool) ([]byte, error)
-
-// FileMetaEqual checks whether 2 FileMeta objects are consistent with eachother
-func FileMetaEqual(actual data.FileMeta, expected data.FileMeta) error {
-	if actual.Length != expected.Length {
-		return ErrWrongLength
-	}
-	hashChecked := false
-	for typ, hash := range expected.Hashes {
-		if h, ok := actual.Hashes[typ]; ok {
-			hashChecked = true
-			if !hmac.Equal(h, hash) {
-				return ErrWrongHash{typ, hash, h}
-			}
-		}
-	}
-	if !hashChecked {
-		return ErrNoCommonHash{expected.Hashes, actual.Hashes}
-	}
-	return nil
-}
-
-// NormalizeTarget adds a slash, if required, to the front of a target path
-func NormalizeTarget(path string) string {
-	return gopath.Join("/", path)
-}
-
-// HashedPaths prefixes the filename with the known hashes for the file,
-// returning a list of possible consistent paths.
-func HashedPaths(path string, hashes data.Hashes) []string {
-	paths := make([]string, 0, len(hashes))
-	for _, hash := range hashes {
-		hashedPath := filepath.Join(filepath.Dir(path), hex.EncodeToString(hash)+"."+filepath.Base(path))
-		paths = append(paths, hashedPath)
-	}
-	return paths
-}
-
-// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
-// On regular RSA/ECDSA TUF keys, this is just the key ID.  On X509 RSA/ECDSA
-// TUF keys, this is the key ID of the public key part of the key in the leaf cert
-func CanonicalKeyID(k data.PublicKey) (string, error) {
-	switch k.Algorithm() {
-	case data.ECDSAx509Key, data.RSAx509Key:
-		return trustmanager.X509PublicKeyID(k)
-	default:
-		return k.ID(), nil
-	}
-}

+ 136 - 109
vendor/src/github.com/docker/notary/trustmanager/x509utils.go → vendor/src/github.com/docker/notary/tuf/utils/x509.go

@@ -1,4 +1,4 @@
-package trustmanager
+package utils
 
 
 import (
 import (
 	"bytes"
 	"bytes"
@@ -22,22 +22,16 @@ import (
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
 )
 )
 
 
-// CertToPEM is a utility function returns a PEM encoded x509 Certificate
-func CertToPEM(cert *x509.Certificate) []byte {
-	pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
-
-	return pemCert
-}
-
-// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
-func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
-	var pemBytes bytes.Buffer
-	for _, cert := range certChain {
-		if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
-			return nil, err
-		}
+// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
+// On regular RSA/ECDSA TUF keys, this is just the key ID.  On X509 RSA/ECDSA
+// TUF keys, this is the key ID of the public key part of the key in the leaf cert
+func CanonicalKeyID(k data.PublicKey) (string, error) {
+	switch k.Algorithm() {
+	case data.ECDSAx509Key, data.RSAx509Key:
+		return X509PublicKeyID(k)
+	default:
+		return k.ID(), nil
 	}
 	}
-	return pemBytes.Bytes(), nil
 }
 }
 
 
 // LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
 // LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
@@ -64,6 +58,108 @@ func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
 	return nil, errors.New("no certificates found in PEM data")
 	return nil, errors.New("no certificates found in PEM data")
 }
 }
 
 
+// X509PublicKeyID returns a public key ID as a string, given a
+// data.PublicKey that contains an X509 Certificate
+func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
+	// Note that this only loads the first certificate from the public key
+	cert, err := LoadCertFromPEM(certPubKey.Public())
+	if err != nil {
+		return "", err
+	}
+	pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
+	if err != nil {
+		return "", err
+	}
+
+	var key data.PublicKey
+	switch certPubKey.Algorithm() {
+	case data.ECDSAx509Key:
+		key = data.NewECDSAPublicKey(pubKeyBytes)
+	case data.RSAx509Key:
+		key = data.NewRSAPublicKey(pubKeyBytes)
+	}
+
+	return key.ID(), nil
+}
+
+// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
+// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
+func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) {
+	block, _ := pem.Decode(pemBytes)
+	if block == nil {
+		return nil, errors.New("no valid private key found")
+	}
+
+	var privKeyBytes []byte
+	var err error
+	if x509.IsEncryptedPEMBlock(block) {
+		privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
+		if err != nil {
+			return nil, errors.New("could not decrypt private key")
+		}
+	} else {
+		privKeyBytes = block.Bytes
+	}
+
+	switch block.Type {
+	case "RSA PRIVATE KEY":
+		rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
+		}
+
+		tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
+		if err != nil {
+			return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
+		}
+
+		return tufRSAPrivateKey, nil
+	case "EC PRIVATE KEY":
+		ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
+		}
+
+		tufECDSAPrivateKey, err := ECDSAToPrivateKey(ecdsaPrivKey)
+		if err != nil {
+			return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
+		}
+
+		return tufECDSAPrivateKey, nil
+	case "ED25519 PRIVATE KEY":
+		// We serialize ED25519 keys by concatenating the private key
+		// to the public key and encoding with PEM. See the
+		// ED25519ToPrivateKey function.
+		tufECDSAPrivateKey, err := ED25519ToPrivateKey(privKeyBytes)
+		if err != nil {
+			return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
+		}
+
+		return tufECDSAPrivateKey, nil
+
+	default:
+		return nil, fmt.Errorf("unsupported key type %q", block.Type)
+	}
+}
+
+// CertToPEM is a utility function returns a PEM encoded x509 Certificate
+func CertToPEM(cert *x509.Certificate) []byte {
+	pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
+
+	return pemCert
+}
+
+// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
+func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
+	var pemBytes bytes.Buffer
+	for _, cert := range certChain {
+		if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
+			return nil, err
+		}
+	}
+	return pemBytes.Bytes(), nil
+}
+
 // LoadCertFromFile loads the first certificate from the file provided. The
 // LoadCertFromFile loads the first certificate from the file provided. The
 // data is expected to be PEM Encoded and contain one of more certificates
 // data is expected to be PEM Encoded and contain one of more certificates
 // with PEM type "CERTIFICATE"
 // with PEM type "CERTIFICATE"
@@ -138,66 +234,6 @@ func GetIntermediateCerts(certs []*x509.Certificate) []*x509.Certificate {
 	return intCerts
 	return intCerts
 }
 }
 
 
-// ParsePEMPrivateKey returns a data.PrivateKey from a PEM encoded private key. It
-// only supports RSA (PKCS#1) and attempts to decrypt using the passphrase, if encrypted.
-func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, error) {
-	block, _ := pem.Decode(pemBytes)
-	if block == nil {
-		return nil, errors.New("no valid private key found")
-	}
-
-	var privKeyBytes []byte
-	var err error
-	if x509.IsEncryptedPEMBlock(block) {
-		privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
-		if err != nil {
-			return nil, errors.New("could not decrypt private key")
-		}
-	} else {
-		privKeyBytes = block.Bytes
-	}
-
-	switch block.Type {
-	case "RSA PRIVATE KEY":
-		rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
-		if err != nil {
-			return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
-		}
-
-		tufRSAPrivateKey, err := RSAToPrivateKey(rsaPrivKey)
-		if err != nil {
-			return nil, fmt.Errorf("could not convert rsa.PrivateKey to data.PrivateKey: %v", err)
-		}
-
-		return tufRSAPrivateKey, nil
-	case "EC PRIVATE KEY":
-		ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
-		if err != nil {
-			return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
-		}
-
-		tufECDSAPrivateKey, err := ECDSAToPrivateKey(ecdsaPrivKey)
-		if err != nil {
-			return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
-		}
-
-		return tufECDSAPrivateKey, nil
-	case "ED25519 PRIVATE KEY":
-		// We serialize ED25519 keys by concatenating the private key
-		// to the public key and encoding with PEM. See the
-		// ED25519ToPrivateKey function.
-		tufECDSAPrivateKey, err := ED25519ToPrivateKey(privKeyBytes)
-		if err != nil {
-			return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
-		}
-
-		return tufECDSAPrivateKey, nil
-
-	default:
-		return nil, fmt.Errorf("unsupported key type %q", block.Type)
-	}
-}
-
 // ParsePEMPublicKey returns a data.PublicKey from a PEM encoded public key or certificate.
 // ParsePEMPublicKey returns a data.PublicKey from a PEM encoded public key or certificate.
 func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
 func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
 	pemBlock, _ := pem.Decode(pubKeyBytes)
 	pemBlock, _ := pem.Decode(pubKeyBytes)
@@ -211,7 +247,7 @@ func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("could not parse provided certificate: %v", err)
 			return nil, fmt.Errorf("could not parse provided certificate: %v", err)
 		}
 		}
-		err = ValidateCertificate(cert)
+		err = ValidateCertificate(cert, true)
 		if err != nil {
 		if err != nil {
 			return nil, fmt.Errorf("invalid certificate: %v", err)
 			return nil, fmt.Errorf("invalid certificate: %v", err)
 		}
 		}
@@ -222,16 +258,15 @@ func ParsePEMPublicKey(pubKeyBytes []byte) (data.PublicKey, error) {
 }
 }
 
 
 // ValidateCertificate returns an error if the certificate is not valid for notary
 // ValidateCertificate returns an error if the certificate is not valid for notary
-// Currently this is only a time expiry check, and ensuring the public key has a large enough modulus if RSA
-func ValidateCertificate(c *x509.Certificate) error {
+// Currently this is only ensuring the public key has a large enough modulus if RSA,
+// using a non SHA1 signature algorithm, and an optional time expiry check
+func ValidateCertificate(c *x509.Certificate, checkExpiry bool) error {
 	if (c.NotBefore).After(c.NotAfter) {
 	if (c.NotBefore).After(c.NotAfter) {
 		return fmt.Errorf("certificate validity window is invalid")
 		return fmt.Errorf("certificate validity window is invalid")
 	}
 	}
-	now := time.Now()
-	tomorrow := now.AddDate(0, 0, 1)
-	// Give one day leeway on creation "before" time, check "after" against today
-	if (tomorrow).Before(c.NotBefore) || now.After(c.NotAfter) {
-		return fmt.Errorf("certificate is expired")
+	// Can't have SHA1 sig algorithm
+	if c.SignatureAlgorithm == x509.SHA1WithRSA || c.SignatureAlgorithm == x509.DSAWithSHA1 || c.SignatureAlgorithm == x509.ECDSAWithSHA1 {
+		return fmt.Errorf("certificate with CN %s uses invalid SHA1 signature algorithm", c.Subject.CommonName)
 	}
 	}
 	// If we have an RSA key, make sure it's long enough
 	// If we have an RSA key, make sure it's long enough
 	if c.PublicKeyAlgorithm == x509.RSA {
 	if c.PublicKeyAlgorithm == x509.RSA {
@@ -243,6 +278,18 @@ func ValidateCertificate(c *x509.Certificate) error {
 			return fmt.Errorf("RSA bit length is too short")
 			return fmt.Errorf("RSA bit length is too short")
 		}
 		}
 	}
 	}
+	if checkExpiry {
+		now := time.Now()
+		tomorrow := now.AddDate(0, 0, 1)
+		// Give one day leeway on creation "before" time, check "after" against today
+		if (tomorrow).Before(c.NotBefore) || now.After(c.NotAfter) {
+			return data.ErrCertExpired{CN: c.Subject.CommonName}
+		}
+		// If this certificate is expiring within 6 months, put out a warning
+		if (c.NotAfter).Before(time.Now().AddDate(0, 6, 0)) {
+			logrus.Warnf("certificate with CN %s is near expiry", c.Subject.CommonName)
+		}
+	}
 	return nil
 	return nil
 }
 }
 
 
@@ -385,7 +432,7 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
 
 
 // EncryptPrivateKey returns an encrypted PEM key given a Privatekey
 // EncryptPrivateKey returns an encrypted PEM key given a Privatekey
 // and a passphrase
 // and a passphrase
-func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, error) {
+func EncryptPrivateKey(key data.PrivateKey, role, gun, passphrase string) ([]byte, error) {
 	bt, err := blockType(key)
 	bt, err := blockType(key)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -408,6 +455,10 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
 	}
 	}
 	encryptedPEMBlock.Headers["role"] = role
 	encryptedPEMBlock.Headers["role"] = role
 
 
+	if gun != "" {
+		encryptedPEMBlock.Headers["gun"] = gun
+	}
+
 	return pem.EncodeToMemory(encryptedPEMBlock), nil
 	return pem.EncodeToMemory(encryptedPEMBlock), nil
 }
 }
 
 
@@ -498,27 +549,3 @@ func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate
 		BasicConstraintsValid: true,
 		BasicConstraintsValid: true,
 	}, nil
 	}, nil
 }
 }
-
-// X509PublicKeyID returns a public key ID as a string, given a
-// data.PublicKey that contains an X509 Certificate
-func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
-	// Note that this only loads the first certificate from the public key
-	cert, err := LoadCertFromPEM(certPubKey.Public())
-	if err != nil {
-		return "", err
-	}
-	pubKeyBytes, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
-	if err != nil {
-		return "", err
-	}
-
-	var key data.PublicKey
-	switch certPubKey.Algorithm() {
-	case data.ECDSAx509Key:
-		key = data.NewECDSAPublicKey(pubKeyBytes)
-	case data.RSAx509Key:
-		key = data.NewRSAPublicKey(pubKeyBytes)
-	}
-
-	return key.ID(), nil
-}