From 09386ff2b9079c39850107839323ff09ad33c75f Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Wed, 5 Apr 2017 12:21:40 -0400 Subject: [PATCH 1/2] vendor docker/go-connections to include the enhacement for decrypting keys Signed-off-by: Arash Deshmeh Upstream-commit: 7180f887ea4204a4a99f0de85ada64cac15856d7 Component: engine --- components/engine/vendor.conf | 2 +- .../docker/go-connections/tlsconfig/config.go | 76 +++++++++++++++++-- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/components/engine/vendor.conf b/components/engine/vendor.conf index 29db3350c1..6cab21fc74 100644 --- a/components/engine/vendor.conf +++ b/components/engine/vendor.conf @@ -17,7 +17,7 @@ github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 golang.org/x/net c427ad74c6d7a814201695e9ffde0c5d400a7674 golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 -github.com/docker/go-connections a2afab9802043837035592f1c24827fb70766de9 +github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 diff --git a/components/engine/vendor/github.com/docker/go-connections/tlsconfig/config.go b/components/engine/vendor/github.com/docker/go-connections/tlsconfig/config.go index 0f8684497e..ad4b112ab3 100644 --- a/components/engine/vendor/github.com/docker/go-connections/tlsconfig/config.go +++ b/components/engine/vendor/github.com/docker/go-connections/tlsconfig/config.go @@ -8,11 +8,13 @@ package tlsconfig import ( "crypto/tls" "crypto/x509" + "encoding/pem" "fmt" "io/ioutil" "os" "github.com/Sirupsen/logrus" + "github.com/pkg/errors" ) // Options represents the information needed to create client and server TLS configurations. @@ -34,6 +36,9 @@ type Options struct { // the system pool will be used. ExclusiveRootPools bool MinVersion uint16 + // If Passphrase is set, it will be used to decrypt a TLS private key + // if the key is encrypted + Passphrase string } // Extra (server-side) accepted CBC cipher suites - will phase out in the future @@ -127,6 +132,67 @@ func adjustMinVersion(options Options, config *tls.Config) error { return nil } +// IsErrEncryptedKey returns true if the 'err' is an error of incorrect +// password when tryin to decrypt a TLS private key +func IsErrEncryptedKey(err error) bool { + return errors.Cause(err) == x509.IncorrectPasswordError +} + +// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. +// If the private key is encrypted, 'passphrase' is used to decrypted the +// private key. +func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { + // this section makes some small changes to code from notary/tuf/utils/x509.go + pemBlock, _ := pem.Decode(keyBytes) + if pemBlock == nil { + return nil, fmt.Errorf("no valid private key found") + } + + var err error + if x509.IsEncryptedPEMBlock(pemBlock) { + keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) + if err != nil { + return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it") + } + keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) + } + + return keyBytes, nil +} + +// getCert returns a Certificate from the CertFile and KeyFile in 'options', +// if the key is encrypted, the Passphrase in 'options' will be used to +// decrypt it. +func getCert(options Options) ([]tls.Certificate, error) { + if options.CertFile == "" && options.KeyFile == "" { + return nil, nil + } + + errMessage := "Could not load X509 key pair" + + cert, err := ioutil.ReadFile(options.CertFile) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + prKeyBytes, err := ioutil.ReadFile(options.KeyFile) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + tlsCert, err := tls.X509KeyPair(cert, prKeyBytes) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + return []tls.Certificate{tlsCert}, nil +} + // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { tlsConfig := ClientDefault() @@ -139,13 +205,11 @@ func Client(options Options) (*tls.Config, error) { tlsConfig.RootCAs = CAs } - if options.CertFile != "" || options.KeyFile != "" { - tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) - if err != nil { - return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) - } - tlsConfig.Certificates = []tls.Certificate{tlsCert} + tlsCerts, err := getCert(options) + if err != nil { + return nil, err } + tlsConfig.Certificates = tlsCerts if err := adjustMinVersion(options, tlsConfig); err != nil { return nil, err From 1f0e0155dc666f9b0a6c963fdb3fd2e7d9edb0a6 Mon Sep 17 00:00:00 2001 From: Arash Deshmeh Date: Sat, 25 Feb 2017 15:17:23 -0500 Subject: [PATCH 2/2] use an encrypted client certificate to connect to a docker daemon Signed-off-by: Arash Deshmeh Upstream-commit: 603dd8b3b48273c0c7e1f2aef5f344acac66424e Component: engine --- components/engine/cli/command/cli.go | 22 ++++++++++++++++++++++ components/engine/client/client_test.go | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/components/engine/cli/command/cli.go b/components/engine/cli/command/cli.go index 6194bae1cc..45752d7d55 100644 --- a/components/engine/cli/command/cli.go +++ b/components/engine/cli/command/cli.go @@ -20,6 +20,7 @@ import ( dopts "github.com/docker/docker/opts" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" + "github.com/docker/notary/passphrase" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -153,9 +154,30 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { var err error cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) + if tlsconfig.IsErrEncryptedKey(err) { + var ( + passwd string + giveup bool + ) + passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil) + + for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ { + // some code and comments borrowed from notary/trustmanager/keystore.go + passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts) + // Check if the passphrase retriever got an error or if it is telling us to give up + if giveup || err != nil { + return errors.Wrap(err, "private key is encrypted, but could not get passphrase") + } + + opts.Common.TLSOptions.Passphrase = passwd + cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile) + } + } + if err != nil { return err } + cli.defaultVersion = cli.client.ClientVersion() if opts.Common.TrustKey == "" { diff --git a/components/engine/client/client_test.go b/components/engine/client/client_test.go index 64188d5fb1..0816d8a9bf 100644 --- a/components/engine/client/client_test.go +++ b/components/engine/client/client_test.go @@ -33,7 +33,7 @@ func TestNewEnvClient(t *testing.T) { envs: map[string]string{ "DOCKER_CERT_PATH": "invalid/path", }, - expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory. Make sure the key is not encrypted", + expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory", }, { envs: map[string]string{