Browse Source

cli: Split out GetNotaryRepository and associated functions

Split these into cli/trust so that other commands can make use of them.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
(cherry picked from commit 4b8c79f25ee00ca5dfe22271c166938009bda976)
Aaron Lehmann 8 years ago
parent
commit
5e7d2ab3b8
3 changed files with 244 additions and 227 deletions
  1. 18 223
      cli/command/image/trust.go
  2. 5 4
      cli/command/image/trust_test.go
  3. 221 0
      cli/trust/trust.go

+ 18 - 223
cli/command/image/trust.go

@@ -6,43 +6,22 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"net"
-	"net/http"
-	"net/url"
-	"os"
 	"path"
 	"path"
-	"path/filepath"
 	"sort"
 	"sort"
-	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/digest"
 	"github.com/docker/distribution/digest"
-	"github.com/docker/distribution/registry/client/auth"
-	"github.com/docker/distribution/registry/client/auth/challenge"
-	"github.com/docker/distribution/registry/client/transport"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
-	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
-	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/cli/trust"
 	"github.com/docker/docker/distribution"
 	"github.com/docker/docker/distribution"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"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/notary"
 	"github.com/docker/notary/client"
 	"github.com/docker/notary/client"
-	"github.com/docker/notary/passphrase"
-	"github.com/docker/notary/storage"
-	"github.com/docker/notary/trustmanager"
-	"github.com/docker/notary/trustpinning"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/data"
-	"github.com/docker/notary/tuf/signed"
-)
-
-var (
-	releasesRole = path.Join(data.CanonicalTargetsRole, "releases")
 )
 )
 
 
 type target struct {
 type target struct {
@@ -118,7 +97,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 
 
 	fmt.Fprintln(cli.Out(), "Signing and pushing trust metadata")
 	fmt.Fprintln(cli.Out(), "Signing and pushing trust metadata")
 
 
-	repo, err := GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
+	repo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.Out(), "Error establishing connection to notary repository: %s\n", err)
 		fmt.Fprintf(cli.Out(), "Error establishing connection to notary repository: %s\n", err)
 		return err
 		return err
@@ -145,7 +124,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([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
 		if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
-			return notaryError(repoInfo.FullName(), err)
+			return trust.NotaryError(repoInfo.FullName(), err)
 		}
 		}
 		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
 		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
 		err = repo.AddTarget(target, data.CanonicalTargetsRole)
 		err = repo.AddTarget(target, data.CanonicalTargetsRole)
@@ -153,7 +132,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 		// already initialized and we have successfully downloaded the latest metadata
 		// already initialized and we have successfully downloaded the latest metadata
 		err = addTargetToAllSignableRoles(repo, target)
 		err = addTargetToAllSignableRoles(repo, target)
 	default:
 	default:
-		return notaryError(repoInfo.FullName(), err)
+		return trust.NotaryError(repoInfo.FullName(), err)
 	}
 	}
 
 
 	if err == nil {
 	if err == nil {
@@ -162,7 +141,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 
 
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.Out(), "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
 		fmt.Fprintf(cli.Out(), "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
-		return notaryError(repoInfo.FullName(), err)
+		return trust.NotaryError(repoInfo.FullName(), err)
 	}
 	}
 
 
 	fmt.Fprintf(cli.Out(), "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
 	fmt.Fprintf(cli.Out(), "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
@@ -235,7 +214,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
 func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
 	var refs []target
 	var refs []target
 
 
-	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
+	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 		return err
 		return err
@@ -243,9 +222,9 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 
 
 	if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
 	if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
 		// List all targets
 		// List all targets
-		targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole)
+		targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
 		if err != nil {
 		if err != nil {
-			return notaryError(repoInfo.FullName(), err)
+			return trust.NotaryError(repoInfo.FullName(), err)
 		}
 		}
 		for _, tgt := range targets {
 		for _, tgt := range targets {
 			t, err := convertTarget(tgt.Target)
 			t, err := convertTarget(tgt.Target)
@@ -255,23 +234,23 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 			}
 			}
 			// Only list tags in the top level targets role or the releases delegation role - ignore
 			// Only list tags in the top level targets role or the releases delegation role - ignore
 			// all other delegation roles
 			// all other delegation roles
-			if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole {
+			if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
 				continue
 				continue
 			}
 			}
 			refs = append(refs, t)
 			refs = append(refs, t)
 		}
 		}
 		if len(refs) == 0 {
 		if len(refs) == 0 {
-			return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
+			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
 		}
 		}
 	} else {
 	} else {
-		t, err := notaryRepo.GetTargetByName(tagged.Tag(), releasesRole, data.CanonicalTargetsRole)
+		t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 		if err != nil {
 		if err != nil {
-			return notaryError(repoInfo.FullName(), err)
+			return trust.NotaryError(repoInfo.FullName(), err)
 		}
 		}
 		// Only get the tag if it's in the top level targets role or the releases delegation role
 		// Only get the tag if it's in the top level targets role or the releases delegation role
 		// ignore it if it's in any other delegation roles
 		// ignore it if it's in any other delegation roles
-		if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
-			return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
+		if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
+			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
 		}
 		}
 
 
 		logrus.Debugf("retrieving target for %s role\n", t.Role)
 		logrus.Debugf("retrieving target for %s role\n", t.Role)
@@ -335,159 +314,6 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
 }
 }
 
 
-func trustDirectory() string {
-	return filepath.Join(cliconfig.ConfigDir(), "trust")
-}
-
-// certificateDirectory returns the directory containing
-// TLS certificates for the given server. An error is
-// returned if there was an error parsing the server string.
-func certificateDirectory(server string) (string, error) {
-	u, err := url.Parse(server)
-	if err != nil {
-		return "", err
-	}
-
-	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
-}
-
-func trustServer(index *registrytypes.IndexInfo) (string, error) {
-	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
-		urlObj, err := url.Parse(s)
-		if err != nil || urlObj.Scheme != "https" {
-			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
-		}
-
-		return s, nil
-	}
-	if index.Official {
-		return registry.NotaryServer, nil
-	}
-	return "https://" + index.Name, nil
-}
-
-type simpleCredentialStore struct {
-	auth types.AuthConfig
-}
-
-func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
-	return scs.auth.Username, scs.auth.Password
-}
-
-func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
-	return scs.auth.IdentityToken
-}
-
-func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
-}
-
-// GetNotaryRepository returns a NotaryRepository which stores all the
-// information needed to operate on a notary repository.
-// It creates an HTTP transport providing authentication support.
-// TODO: move this too
-func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
-	server, err := trustServer(repoInfo.Index)
-	if err != nil {
-		return nil, err
-	}
-
-	var cfg = tlsconfig.ClientDefault()
-	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
-
-	// Get certificate base directory
-	certDir, err := certificateDirectory(server)
-	if err != nil {
-		return nil, err
-	}
-	logrus.Debugf("reading certificate directory: %s", certDir)
-
-	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
-		return nil, err
-	}
-
-	base := &http.Transport{
-		Proxy: http.ProxyFromEnvironment,
-		Dial: (&net.Dialer{
-			Timeout:   30 * time.Second,
-			KeepAlive: 30 * time.Second,
-			DualStack: true,
-		}).Dial,
-		TLSHandshakeTimeout: 10 * time.Second,
-		TLSClientConfig:     cfg,
-		DisableKeepAlives:   true,
-	}
-
-	// Skip configuration headers since request is not going to Docker daemon
-	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
-	authTransport := transport.NewTransport(base, modifiers...)
-	pingClient := &http.Client{
-		Transport: authTransport,
-		Timeout:   5 * time.Second,
-	}
-	endpointStr := server + "/v2/"
-	req, err := http.NewRequest("GET", endpointStr, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	challengeManager := challenge.NewSimpleManager()
-
-	resp, err := pingClient.Do(req)
-	if err != nil {
-		// Ignore error on ping to operate in offline mode
-		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
-	} else {
-		defer resp.Body.Close()
-
-		// Add response to the challenge manager to parse out
-		// authentication header and register authentication method
-		if err := challengeManager.AddResponse(resp); err != nil {
-			return nil, err
-		}
-	}
-
-	creds := simpleCredentialStore{auth: authConfig}
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
-	basicHandler := auth.NewBasicHandler(creds)
-	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
-	tr := transport.NewTransport(base, modifiers...)
-
-	return client.NewNotaryRepository(
-		trustDirectory(),
-		repoInfo.FullName(),
-		server,
-		tr,
-		getPassphraseRetriever(streams),
-		trustpinning.TrustPinConfig{})
-}
-
-func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
-	aliasMap := map[string]string{
-		"root":     "root",
-		"snapshot": "repository",
-		"targets":  "repository",
-		"default":  "repository",
-	}
-	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
-	env := map[string]string{
-		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
-		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
-		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
-		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
-	}
-
-	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
-		if v := env[alias]; v != "" {
-			return v, numAttempts > 1, nil
-		}
-		// For non-root roles, we can also try the "default" alias if it is specified
-		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
-			return v, numAttempts > 1, nil
-		}
-		return baseRetriever(keyName, alias, createNew, numAttempts)
-	}
-}
-
 // TrustedReference returns the canonical trusted reference for an image reference
 // TrustedReference returns the canonical trusted reference for an image reference
 func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
 func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
@@ -498,20 +324,20 @@ func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference
 	// Resolve the Auth config relevant for this server
 	// Resolve the Auth config relevant for this server
 	authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
 	authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
 
 
-	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
+	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole)
+	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	// Only list tags in the top level targets role or the releases delegation role - ignore
 	// Only list tags in the top level targets role or the releases delegation role - ignore
 	// all other delegation roles
 	// all other delegation roles
-	if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
-		return nil, notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
+	if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
+		return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
 	}
 	}
 	r, err := convertTarget(t.Target)
 	r, err := convertTarget(t.Target)
 	if err != nil {
 	if err != nil {
@@ -540,34 +366,3 @@ func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef referenc
 
 
 	return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String())
 	return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String())
 }
 }
-
-// notaryError formats an error message received from the notary service
-func notaryError(repoName string, err error) error {
-	switch err.(type) {
-	case *json.SyntaxError:
-		logrus.Debugf("Notary syntax error: %s", err)
-		return fmt.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
-	case signed.ErrExpired:
-		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
-	case trustmanager.ErrKeyNotFound:
-		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
-	case storage.NetworkError:
-		return fmt.Errorf("Error: error contacting notary server: %v", err)
-	case storage.ErrMetaNotFound:
-		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
-	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
-		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
-	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)
-	case signed.ErrLowVersion:
-		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
-	case signed.ErrRoleThreshold:
-		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
-	case client.ErrRepositoryNotExist:
-		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
-	case signed.ErrInsufficientSignatures:
-		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
-	}
-
-	return err
-}

+ 5 - 4
cli/command/image/trust_test.go

@@ -5,6 +5,7 @@ import (
 	"testing"
 	"testing"
 
 
 	registrytypes "github.com/docker/docker/api/types/registry"
 	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/cli/trust"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 )
 )
 
 
@@ -19,7 +20,7 @@ func TestENVTrustServer(t *testing.T) {
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
 		t.Fatal("Failed to set ENV variable")
 		t.Fatal("Failed to set ENV variable")
 	}
 	}
-	output, err := trustServer(indexInfo)
+	output, err := trust.Server(indexInfo)
 	expectedStr := "https://notary-test.com:5000"
 	expectedStr := "https://notary-test.com:5000"
 	if err != nil || output != expectedStr {
 	if err != nil || output != expectedStr {
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
@@ -32,7 +33,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
 		t.Fatal("Failed to set ENV variable")
 		t.Fatal("Failed to set ENV variable")
 	}
 	}
-	_, err := trustServer(indexInfo)
+	_, err := trust.Server(indexInfo)
 	if err == nil {
 	if err == nil {
 		t.Fatal("Expected error with invalid scheme")
 		t.Fatal("Expected error with invalid scheme")
 	}
 	}
@@ -40,7 +41,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
 
 
 func TestOfficialTrustServer(t *testing.T) {
 func TestOfficialTrustServer(t *testing.T) {
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: true}
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: true}
-	output, err := trustServer(indexInfo)
+	output, err := trust.Server(indexInfo)
 	if err != nil || output != registry.NotaryServer {
 	if err != nil || output != registry.NotaryServer {
 		t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
 		t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
 	}
 	}
@@ -48,7 +49,7 @@ func TestOfficialTrustServer(t *testing.T) {
 
 
 func TestNonOfficialTrustServer(t *testing.T) {
 func TestNonOfficialTrustServer(t *testing.T) {
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: false}
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: false}
-	output, err := trustServer(indexInfo)
+	output, err := trust.Server(indexInfo)
 	expectedStr := "https://" + indexInfo.Name
 	expectedStr := "https://" + indexInfo.Name
 	if err != nil || output != expectedStr {
 	if err != nil || output != expectedStr {
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)

+ 221 - 0
cli/trust/trust.go

@@ -0,0 +1,221 @@
+package trust
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"path/filepath"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/registry/client/auth"
+	"github.com/docker/distribution/registry/client/auth/challenge"
+	"github.com/docker/distribution/registry/client/transport"
+	"github.com/docker/docker/api/types"
+	registrytypes "github.com/docker/docker/api/types/registry"
+	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/registry"
+	"github.com/docker/go-connections/tlsconfig"
+	"github.com/docker/notary"
+	"github.com/docker/notary/client"
+	"github.com/docker/notary/passphrase"
+	"github.com/docker/notary/storage"
+	"github.com/docker/notary/trustmanager"
+	"github.com/docker/notary/trustpinning"
+	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/signed"
+)
+
+var (
+	// ReleasesRole is the role named "releases"
+	ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases")
+)
+
+func trustDirectory() string {
+	return filepath.Join(cliconfig.ConfigDir(), "trust")
+}
+
+// certificateDirectory returns the directory containing
+// TLS certificates for the given server. An error is
+// returned if there was an error parsing the server string.
+func certificateDirectory(server string) (string, error) {
+	u, err := url.Parse(server)
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
+}
+
+// Server returns the base URL for the trust server.
+func Server(index *registrytypes.IndexInfo) (string, error) {
+	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
+		urlObj, err := url.Parse(s)
+		if err != nil || urlObj.Scheme != "https" {
+			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
+		}
+
+		return s, nil
+	}
+	if index.Official {
+		return registry.NotaryServer, nil
+	}
+	return "https://" + index.Name, nil
+}
+
+type simpleCredentialStore struct {
+	auth types.AuthConfig
+}
+
+func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
+	return scs.auth.Username, scs.auth.Password
+}
+
+func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
+	return scs.auth.IdentityToken
+}
+
+func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
+}
+
+// GetNotaryRepository returns a NotaryRepository which stores all the
+// information needed to operate on a notary repository.
+// It creates an HTTP transport providing authentication support.
+func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
+	server, err := Server(repoInfo.Index)
+	if err != nil {
+		return nil, err
+	}
+
+	var cfg = tlsconfig.ClientDefault()
+	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
+
+	// Get certificate base directory
+	certDir, err := certificateDirectory(server)
+	if err != nil {
+		return nil, err
+	}
+	logrus.Debugf("reading certificate directory: %s", certDir)
+
+	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
+		return nil, err
+	}
+
+	base := &http.Transport{
+		Proxy: http.ProxyFromEnvironment,
+		Dial: (&net.Dialer{
+			Timeout:   30 * time.Second,
+			KeepAlive: 30 * time.Second,
+			DualStack: true,
+		}).Dial,
+		TLSHandshakeTimeout: 10 * time.Second,
+		TLSClientConfig:     cfg,
+		DisableKeepAlives:   true,
+	}
+
+	// Skip configuration headers since request is not going to Docker daemon
+	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
+	authTransport := transport.NewTransport(base, modifiers...)
+	pingClient := &http.Client{
+		Transport: authTransport,
+		Timeout:   5 * time.Second,
+	}
+	endpointStr := server + "/v2/"
+	req, err := http.NewRequest("GET", endpointStr, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	challengeManager := challenge.NewSimpleManager()
+
+	resp, err := pingClient.Do(req)
+	if err != nil {
+		// Ignore error on ping to operate in offline mode
+		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
+	} else {
+		defer resp.Body.Close()
+
+		// Add response to the challenge manager to parse out
+		// authentication header and register authentication method
+		if err := challengeManager.AddResponse(resp); err != nil {
+			return nil, err
+		}
+	}
+
+	creds := simpleCredentialStore{auth: authConfig}
+	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
+	basicHandler := auth.NewBasicHandler(creds)
+	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
+	tr := transport.NewTransport(base, modifiers...)
+
+	return client.NewNotaryRepository(
+		trustDirectory(),
+		repoInfo.FullName(),
+		server,
+		tr,
+		getPassphraseRetriever(streams),
+		trustpinning.TrustPinConfig{})
+}
+
+func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
+	aliasMap := map[string]string{
+		"root":     "root",
+		"snapshot": "repository",
+		"targets":  "repository",
+		"default":  "repository",
+	}
+	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
+	env := map[string]string{
+		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
+		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
+		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
+		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
+	}
+
+	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
+		if v := env[alias]; v != "" {
+			return v, numAttempts > 1, nil
+		}
+		// For non-root roles, we can also try the "default" alias if it is specified
+		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
+			return v, numAttempts > 1, nil
+		}
+		return baseRetriever(keyName, alias, createNew, numAttempts)
+	}
+}
+
+// NotaryError formats an error message received from the notary service
+func NotaryError(repoName string, err error) error {
+	switch err.(type) {
+	case *json.SyntaxError:
+		logrus.Debugf("Notary syntax error: %s", err)
+		return fmt.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName)
+	case signed.ErrExpired:
+		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
+	case trustmanager.ErrKeyNotFound:
+		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
+	case storage.NetworkError:
+		return fmt.Errorf("Error: error contacting notary server: %v", err)
+	case storage.ErrMetaNotFound:
+		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
+	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
+		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
+	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)
+	case signed.ErrLowVersion:
+		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
+	case signed.ErrRoleThreshold:
+		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
+	case client.ErrRepositoryNotExist:
+		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
+	case signed.ErrInsufficientSignatures:
+		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
+	}
+
+	return err
+}