From 604bc3f22de26989b6836b4640bd1c093da79f0b Mon Sep 17 00:00:00 2001 From: Riyaz Faizullabhoy Date: Tue, 26 Sep 2017 17:16:18 -0700 Subject: [PATCH] trust: key-load and key-generate code Signed-off-by: Riyaz Faizullabhoy --- cli/command/trust/helpers_test.go | 25 +++ cli/command/trust/key_generate.go | 122 +++++++++++++++ cli/command/trust/key_generate_test.go | 153 ++++++++++++++++++ cli/command/trust/key_load.go | 90 +++++++++++ cli/command/trust/key_load_test.go | 208 +++++++++++++++++++++++++ 5 files changed, 598 insertions(+) create mode 100644 cli/command/trust/helpers_test.go create mode 100644 cli/command/trust/key_generate.go create mode 100644 cli/command/trust/key_generate_test.go create mode 100644 cli/command/trust/key_load.go create mode 100644 cli/command/trust/key_load_test.go diff --git a/cli/command/trust/helpers_test.go b/cli/command/trust/helpers_test.go new file mode 100644 index 0000000000..c3113c65ec --- /dev/null +++ b/cli/command/trust/helpers_test.go @@ -0,0 +1,25 @@ +package trust + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/docker/notary/client" + "github.com/docker/notary/passphrase" + "github.com/docker/notary/trustpinning" + + "github.com/stretchr/testify/assert" +) + +func TestGetOrGenerateNotaryKeyAndInitRepo(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "notary-test-") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + + notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever(passwd), trustpinning.TrustPinConfig{}) + assert.NoError(t, err) + + err = getOrGenerateRootKeyAndInitRepo(notaryRepo) + assert.EqualError(t, err, "client is offline") +} diff --git a/cli/command/trust/key_generate.go b/cli/command/trust/key_generate.go new file mode 100644 index 0000000000..2747dd870a --- /dev/null +++ b/cli/command/trust/key_generate.go @@ -0,0 +1,122 @@ +package trust + +import ( + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/trust" + "github.com/docker/notary" + "github.com/docker/notary/trustmanager" + "github.com/docker/notary/tuf/data" + tufutils "github.com/docker/notary/tuf/utils" + "github.com/spf13/cobra" +) + +func newKeyGenerateCommand(dockerCli command.Streams) *cobra.Command { + cmd := &cobra.Command{ + Use: "key-generate NAME [NAME...]", + Short: "Generate and load a signing key-pair", + Args: cli.RequiresMinArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return setupPassphraseAndGenerateKeys(dockerCli, args) + }, + } + return cmd +} + +// key names can use alphanumeric + _ + - characters +var validKeyName = regexp.MustCompile(`^[a-zA-Z0-9\_]+[a-zA-Z0-9\_\-]*$`).MatchString + +// validate that all of the key names are unique and are alphanumeric + _ + - +// and that we do not already have public key files in the current dir on disk +func validateKeyArgs(keyNames []string, cwdPath string) error { + uniqueKeyNames := map[string]struct{}{} + for _, keyName := range keyNames { + if !validKeyName(keyName) { + return fmt.Errorf("key name \"%s\" must not contain special characters", keyName) + } + + if _, ok := uniqueKeyNames[keyName]; ok { + return fmt.Errorf("key names must be unique, found duplicate key name: \"%s\"", keyName) + } + uniqueKeyNames[keyName] = struct{}{} + + pubKeyFileName := keyName + ".pub" + if _, err := os.Stat(filepath.Join(cwdPath, pubKeyFileName)); err == nil { + return fmt.Errorf("public key file already exists: \"%s\"", pubKeyFileName) + } + } + return nil +} + +func setupPassphraseAndGenerateKeys(streams command.Streams, keyNames []string) error { + // always use a fresh passphrase for each key generation + freshPassRetGetter := func() notary.PassRetriever { return trust.GetPassphraseRetriever(streams.In(), streams.Out()) } + cwd, err := os.Getwd() + if err != nil { + return err + } + return generateKeys(streams, keyNames, cwd, freshPassRetGetter) +} + +func generateKeys(streams command.Streams, keyNames []string, workingDir string, passphraseGetter func() notary.PassRetriever) error { + var genKeyErrs []string + if err := validateKeyArgs(keyNames, workingDir); err != nil { + return err + } + for _, keyName := range keyNames { + fmt.Fprintf(streams.Out(), "\nGenerating key for %s...\n", keyName) + freshPassRet := passphraseGetter() + if err := generateKey(keyName, workingDir, trust.GetTrustDirectory(), freshPassRet); err != nil { + fmt.Fprintf(streams.Out(), err.Error()) + genKeyErrs = append(genKeyErrs, keyName) + } else { + pubFileName := strings.Join([]string{keyName, "pub"}, ".") + fmt.Fprintf(streams.Out(), "Successfully generated and loaded private key. Corresponding public key available: %s\n", pubFileName) + } + } + + if len(genKeyErrs) > 0 { + return fmt.Errorf("Error generating keys for: %s", strings.Join(genKeyErrs, ", ")) + } + return nil +} + +func generateKey(keyName, pubDir, privTrustDir string, passRet notary.PassRetriever) error { + privKey, err := tufutils.GenerateKey(data.ECDSAKey) + if err != nil { + return err + } + + // Automatically load the private key to local storage for use + privKeyFileStore, err := trustmanager.NewKeyFileStore(privTrustDir, passRet) + if err != nil { + return err + } + + privKeyFileStore.AddKey(trustmanager.KeyInfo{Role: data.RoleName(keyName)}, privKey) + if err != nil { + return err + } + + pubKey := data.PublicKeyFromPrivate(privKey) + pubPEM := pem.Block{ + Type: "PUBLIC KEY", + Headers: map[string]string{ + "role": keyName, + }, + Bytes: pubKey.Public(), + } + + // Output the public key to a file in the CWD + pubFileName := strings.Join([]string{keyName, "pub"}, ".") + pubFilePath := filepath.Join(pubDir, pubFileName) + return ioutil.WriteFile(pubFilePath, pem.EncodeToMemory(&pubPEM), notary.PrivNoExecPerms) +} diff --git a/cli/command/trust/key_generate_test.go b/cli/command/trust/key_generate_test.go new file mode 100644 index 0000000000..7c753fa13e --- /dev/null +++ b/cli/command/trust/key_generate_test.go @@ -0,0 +1,153 @@ +package trust + +import ( + "encoding/pem" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/docker/cli/cli/config" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" + "github.com/docker/notary" + "github.com/docker/notary/passphrase" + tufutils "github.com/docker/notary/tuf/utils" + "github.com/stretchr/testify/assert" +) + +func TestTrustKeyGenerateErrors(t *testing.T) { + testCases := []struct { + name string + args []string + expectedError string + }{ + { + name: "not-enough-args", + expectedError: "requires at least 1 argument", + }, + } + tmpDir, err := ioutil.TempDir("", "docker-key-generate-test-") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + config.SetDir(tmpDir) + + for _, tc := range testCases { + cli := test.NewFakeCli(&fakeClient{}) + cmd := newKeyGenerateCommand(cli) + cmd.SetArgs(tc.args) + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} + +func TestGenerateKeySuccess(t *testing.T) { + pubKeyCWD, err := ioutil.TempDir("", "pub-keys-") + assert.NoError(t, err) + defer os.RemoveAll(pubKeyCWD) + + privKeyStorageDir, err := ioutil.TempDir("", "priv-keys-") + assert.NoError(t, err) + defer os.RemoveAll(privKeyStorageDir) + + passwd := "password" + cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) + // generate a single key + keyName := "alice" + assert.NoError(t, generateKey(keyName, pubKeyCWD, privKeyStorageDir, cannedPasswordRetriever)) + + // check that the public key exists: + expectedPubKeyPath := filepath.Join(pubKeyCWD, keyName+".pub") + _, err = os.Stat(expectedPubKeyPath) + assert.NoError(t, err) + // check that the public key is the only file output in CWD + cwdKeyFiles, err := ioutil.ReadDir(pubKeyCWD) + assert.NoError(t, err) + assert.Len(t, cwdKeyFiles, 1) + + // verify the key header is set with the specified name + from, _ := os.OpenFile(expectedPubKeyPath, os.O_RDONLY, notary.PrivExecPerms) + defer from.Close() + fromBytes, _ := ioutil.ReadAll(from) + keyPEM, _ := pem.Decode(fromBytes) + assert.Equal(t, keyName, keyPEM.Headers["role"]) + // the default GUN is empty + assert.Equal(t, "", keyPEM.Headers["gun"]) + // assert public key header + assert.Equal(t, "PUBLIC KEY", keyPEM.Type) + + // check that an appropriate ~//private/.key file exists + expectedPrivKeyDir := filepath.Join(privKeyStorageDir, notary.PrivDir) + _, err = os.Stat(expectedPrivKeyDir) + assert.NoError(t, err) + + keyFiles, err := ioutil.ReadDir(expectedPrivKeyDir) + assert.NoError(t, err) + assert.Len(t, keyFiles, 1) + privKeyFilePath := filepath.Join(expectedPrivKeyDir, keyFiles[0].Name()) + + // verify the key content + privFrom, _ := os.OpenFile(privKeyFilePath, os.O_RDONLY, notary.PrivExecPerms) + defer privFrom.Close() + fromBytes, _ = ioutil.ReadAll(privFrom) + keyPEM, _ = pem.Decode(fromBytes) + assert.Equal(t, keyName, keyPEM.Headers["role"]) + // the default GUN is empty + assert.Equal(t, "", keyPEM.Headers["gun"]) + // assert encrypted header + assert.Equal(t, "ENCRYPTED PRIVATE KEY", keyPEM.Type) + // check that the passphrase matches + _, err = tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd)) + assert.NoError(t, err) +} + +func TestValidateKeyArgs(t *testing.T) { + pubKeyCWD, err := ioutil.TempDir("", "pub-keys-") + assert.NoError(t, err) + defer os.RemoveAll(pubKeyCWD) + + err = validateKeyArgs([]string{"a", "b", "C_123", "key-name"}, pubKeyCWD) + assert.NoError(t, err) + + err = validateKeyArgs([]string{"a", "a"}, pubKeyCWD) + assert.Error(t, err) + assert.Equal(t, err.Error(), "key names must be unique, found duplicate key name: \"a\"") + + err = validateKeyArgs([]string{"a/b"}, pubKeyCWD) + assert.Error(t, err) + assert.Equal(t, err.Error(), "key name \"a/b\" must not contain special characters") + + err = validateKeyArgs([]string{"-"}, pubKeyCWD) + assert.Error(t, err) + assert.Equal(t, err.Error(), "key name \"-\" must not contain special characters") + + assert.NoError(t, ioutil.WriteFile(filepath.Join(pubKeyCWD, "a.pub"), []byte("abc"), notary.PrivExecPerms)) + err = validateKeyArgs([]string{"a"}, pubKeyCWD) + assert.Error(t, err) + assert.Equal(t, err.Error(), "public key file already exists: \"a.pub\"") +} + +func TestGenerateMultipleKeysOutput(t *testing.T) { + pubKeyCWD, err := ioutil.TempDir("", "pub-keys-") + assert.NoError(t, err) + defer os.RemoveAll(pubKeyCWD) + + passwd := "password" + cannedPasswordRetriever := func() notary.PassRetriever { return passphrase.ConstantRetriever(passwd) } + + cli := test.NewFakeCli(&fakeClient{}) + assert.NoError(t, generateKeys(cli, []string{"alice", "bob", "charlie"}, pubKeyCWD, cannedPasswordRetriever)) + + // Check the stdout prints: + assert.Contains(t, cli.OutBuffer().String(), "\nGenerating key for alice...\n") + assert.Contains(t, cli.OutBuffer().String(), "Successfully generated and loaded private key. Corresponding public key available: alice.pub\n") + assert.Contains(t, cli.OutBuffer().String(), "\nGenerating key for bob...\n") + assert.Contains(t, cli.OutBuffer().String(), "Successfully generated and loaded private key. Corresponding public key available: bob.pub\n") + assert.Contains(t, cli.OutBuffer().String(), "\nGenerating key for charlie...\n") + assert.Contains(t, cli.OutBuffer().String(), "Successfully generated and loaded private key. Corresponding public key available: charlie.pub\n") + + // Check that we have three key files: + cwdKeyFiles, err := ioutil.ReadDir(pubKeyCWD) + assert.NoError(t, err) + assert.Len(t, cwdKeyFiles, 3) +} diff --git a/cli/command/trust/key_load.go b/cli/command/trust/key_load.go new file mode 100644 index 0000000000..22fa585803 --- /dev/null +++ b/cli/command/trust/key_load.go @@ -0,0 +1,90 @@ +package trust + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/trust" + "github.com/docker/notary" + "github.com/docker/notary/storage" + tufutils "github.com/docker/notary/tuf/utils" + "github.com/docker/notary/utils" + "github.com/spf13/cobra" +) + +const ( + ownerReadOnlyPerms = 0400 + ownerReadAndWritePerms = 0600 +) + +type keyLoadOptions struct { + keyName string +} + +func newKeyLoadCommand(dockerCli command.Streams) *cobra.Command { + var options keyLoadOptions + cmd := &cobra.Command{ + Use: "key-load [OPTIONS] KEY", + Short: "Load a private key file for signing", + Args: cli.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return loadPrivKey(dockerCli, args[0], options) + }, + } + flags := cmd.Flags() + flags.StringVarP(&options.keyName, "name", "n", "signer", "Name for the loaded key") + return cmd +} + +func loadPrivKey(streams command.Streams, keyPath string, options keyLoadOptions) error { + trustDir := trust.GetTrustDirectory() + keyFileStore, err := storage.NewPrivateKeyFileStorage(filepath.Join(trustDir, notary.PrivDir), notary.KeyExtension) + if err != nil { + return err + } + privKeyImporters := []utils.Importer{keyFileStore} + + fmt.Fprintf(streams.Out(), "\nLoading key from \"%s\"...\n", keyPath) + + // Always use a fresh passphrase retriever for each import + passRet := trust.GetPassphraseRetriever(streams.In(), streams.Out()) + if err := loadPrivKeyFromPath(privKeyImporters, keyPath, options.keyName, passRet); err != nil { + return fmt.Errorf("error importing key from %s: %s", keyPath, err) + } + fmt.Fprintf(streams.Out(), "Successfully imported key from %s\n", keyPath) + return nil +} + +func loadPrivKeyFromPath(privKeyImporters []utils.Importer, keyPath, keyName string, passRet notary.PassRetriever) error { + fileInfo, err := os.Stat(keyPath) + if err != nil { + return err + } + if fileInfo.Mode() != ownerReadOnlyPerms && fileInfo.Mode() != ownerReadAndWritePerms { + return fmt.Errorf("private key permission from %s should be set to 400 or 600", keyPath) + } + + from, err := os.OpenFile(keyPath, os.O_RDONLY, notary.PrivExecPerms) + if err != nil { + return err + } + defer from.Close() + + keyBytes, err := ioutil.ReadAll(from) + if err != nil { + return err + } + if _, _, err := tufutils.ExtractPrivateKeyAttributes(keyBytes); err != nil { + return fmt.Errorf("provided file %s is not a supported private key - to add a signer's public key use docker trust signer-add", keyPath) + } + // Rewind the file pointer + if _, err := from.Seek(0, 0); err != nil { + return err + } + + return utils.ImportKeys(from, privKeyImporters, keyName, "", passRet) +} diff --git a/cli/command/trust/key_load_test.go b/cli/command/trust/key_load_test.go new file mode 100644 index 0000000000..ed878e5e79 --- /dev/null +++ b/cli/command/trust/key_load_test.go @@ -0,0 +1,208 @@ +package trust + +import ( + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/docker/cli/cli/config" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" + "github.com/docker/notary" + "github.com/docker/notary/passphrase" + "github.com/docker/notary/storage" + tufutils "github.com/docker/notary/tuf/utils" + "github.com/docker/notary/utils" + "github.com/stretchr/testify/assert" +) + +func TestTrustKeyLoadErrors(t *testing.T) { + testCases := []struct { + name string + args []string + expectedError string + expectedOutput string + }{ + { + name: "not-enough-args", + expectedError: "exactly 1 argument", + expectedOutput: "", + }, + { + name: "too-many-args", + args: []string{"iamnotakey", "alsonotakey"}, + expectedError: "exactly 1 argument", + expectedOutput: "", + }, + { + name: "not-a-key", + args: []string{"iamnotakey"}, + expectedError: "error importing key from iamnotakey: stat iamnotakey: no such file or directory", + expectedOutput: "\nLoading key from \"iamnotakey\"...\n", + }, + } + tmpDir, err := ioutil.TempDir("", "docker-key-load-test-") + assert.NoError(t, err) + defer os.RemoveAll(tmpDir) + config.SetDir(tmpDir) + + for _, tc := range testCases { + cli := test.NewFakeCli(&fakeClient{}) + cmd := newKeyLoadCommand(cli) + cmd.SetArgs(tc.args) + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + assert.Contains(t, cli.OutBuffer().String(), tc.expectedOutput) + } +} + +var privKeyFixture = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAs7yVMzCw8CBZPoN+QLdx3ZzbVaHnouHIKu+ynX60IZ3stpbb +6rowu78OWON252JcYJqe++2GmdIgbBhg+mZDwhX0ZibMVztJaZFsYL+Ch/2J9KqD +A5NtE1s/XdhYoX5hsv7W4ok9jLFXRYIMj+T4exJRlR4f4GP9p0fcqPWd9/enPnlJ +JFTmu0DXJTZUMVS1UrXUy5t/DPXdrwyl8pM7VCqO3bqK7jqE6mWawdTkEeiku1fJ +ydP0285uiYTbj1Q38VVhPwXzMuLbkaUgRJhCI4BcjfQIjtJLbWpS+VdhUEvtgMVx +XJMKxCVGG69qjXyj9TjI7pxanb/bWglhovJN9wIDAQABAoIBAQCSnMsLxbUfOxPx +RWuwOLN+NZxIvtfnastQEtSdWiRvo5Xa3zYmw5hLHa8DXRC57+cwug/jqr54LQpb +gotg1hiBck05In7ezTK2FXTVeoJskal91bUnLpP0DSOkVnz9xszFKNF6Wr7FTEfH +IC1FF16Fbcz0mW0hKg9X6+uYOzqPcKpQRwli5LAwhT18Alf9h4/3NCeKotiJyr2J +xvcEH1eY2m2c/jQZurBkys7qBC3+i8LJEOW8MBQt7mxajwfbU91wtP2YoqMcoYiS +zsPbYp7Ui2t4G9Yn+OJw+uj4RGP1Bo4nSyRxWDtg+8Zug/JYU6/s+8kVRpiGffd3 +T1GvoxUhAoGBAOnPDWG/g1xlJf65Rh71CxMs638zhYbIloU2K4Rqr05DHe7GryTS +9hLVrwhHddK+KwfVbR8HFMPo1DC/NVbuKt8StTAadAu3HsC088gWd28nOiGAWuvH +Bo3x/DYQGYwGFfoo4rzCOgMj6DJjXmcWEXNv3NDMoXoYpkxa0g6zZDyHAoGBAMTL +t7EUneJT+Mm7wyL1I5bmaT/HFwqoUQB2ccBPVD8p1el62NgLdfhOa8iNlBVhMrlh +2aTjrMlSPcjr9sCgKrLcenSWw+2qFsf4+SmV01ntB9kWes2phXpnB0ynXIcbeG05 ++BLxbqDTVV0Iqh4r/dGeplyV2WyL3mTpkT3hRq8RAoGAZ93degEUICWnHWO9LN97 +Dge0joua0+ekRoVsC6VBP6k9UOfewqMdQfy/hxQH2Zk1kINVuKTyqp1yNj2bOoUP +co3jA/2cc9/jv4QjkE26vRxWDK/ytC90T/aiLno0fyns9XbYUzaNgvuemVPfijgZ +hIi7Nd7SFWWB6wWlr3YuH10CgYEAwh7JVa2mh8iZEjVaKTNyJbmmfDjgq6yYKkKr +ti0KRzv3O9Xn7ERx27tPaobtWaGFLYQt8g57NCMhuv23aw8Sz1fYmwTUw60Rx7P5 +42FdF8lOAn/AJvpfJfxXIO+9v7ADPIr//3+TxqRwAdM4K4btWkaKh61wyTe26gfT +MxzyYmECgYAnlU5zsGyiZqwoXVktkhtZrE7Qu0SoztzFb8KpvFNmMTPF1kAAYmJY +GIhbizeGJ3h4cUdozKmt8ZWIt6uFDEYCqEA7XF4RH75dW25x86mpIPO7iRl9eisY +IsLeMYqTIwXAwGx6Ka9v5LOL1kzcHQ2iVj6+QX+yoptSft1dYa9jOA== +-----END RSA PRIVATE KEY-----`) + +const privKeyID = "ee69e8e07a14756ad5ff0aca2336b37f86b0ac1710d1f3e94440081e080aecd7" + +func TestLoadKeyFromPath(t *testing.T) { + privKeyDir, err := ioutil.TempDir("", "key-load-test-") + assert.NoError(t, err) + defer os.RemoveAll(privKeyDir) + privKeyFilepath := filepath.Join(privKeyDir, "privkey.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, notary.PrivNoExecPerms)) + + keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") + assert.NoError(t, err) + defer os.RemoveAll(keyStorageDir) + + passwd := "password" + cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) + keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) + assert.NoError(t, err) + privKeyImporters := []utils.Importer{keyFileStore} + + // import the key to our keyStorageDir + assert.NoError(t, loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer-name", cannedPasswordRetriever)) + + // check that the appropriate ~//private/.key file exists + expectedImportKeyPath := filepath.Join(keyStorageDir, notary.PrivDir, privKeyID+"."+notary.KeyExtension) + _, err = os.Stat(expectedImportKeyPath) + assert.NoError(t, err) + + // verify the key content + from, _ := os.OpenFile(expectedImportKeyPath, os.O_RDONLY, notary.PrivExecPerms) + defer from.Close() + fromBytes, _ := ioutil.ReadAll(from) + keyPEM, _ := pem.Decode(fromBytes) + assert.Equal(t, "signer-name", keyPEM.Headers["role"]) + // the default GUN is empty + assert.Equal(t, "", keyPEM.Headers["gun"]) + // assert encrypted header + assert.Equal(t, "ENCRYPTED PRIVATE KEY", keyPEM.Type) + + decryptedKey, err := tufutils.ParsePKCS8ToTufKey(keyPEM.Bytes, []byte(passwd)) + assert.NoError(t, err) + fixturePEM, _ := pem.Decode(privKeyFixture) + assert.Equal(t, fixturePEM.Bytes, decryptedKey.Private()) +} + +func TestLoadKeyTooPermissive(t *testing.T) { + privKeyDir, err := ioutil.TempDir("", "key-load-test-") + assert.NoError(t, err) + defer os.RemoveAll(privKeyDir) + privKeyFilepath := filepath.Join(privKeyDir, "privkey477.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0477)) + + keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") + assert.NoError(t, err) + defer os.RemoveAll(keyStorageDir) + + passwd := "password" + cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) + keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) + assert.NoError(t, err) + privKeyImporters := []utils.Importer{keyFileStore} + + // import the key to our keyStorageDir + err = loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer", cannedPasswordRetriever) + assert.Error(t, err) + assert.Contains(t, fmt.Sprintf("private key permission from %s should be set to 400 or 600", privKeyFilepath), err.Error()) + + privKeyFilepath = filepath.Join(privKeyDir, "privkey667.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0677)) + + err = loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer", cannedPasswordRetriever) + assert.Error(t, err) + assert.Contains(t, fmt.Sprintf("private key permission from %s should be set to 400 or 600", privKeyFilepath), err.Error()) + + privKeyFilepath = filepath.Join(privKeyDir, "privkey777.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0777)) + + err = loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer", cannedPasswordRetriever) + assert.Error(t, err) + assert.Contains(t, fmt.Sprintf("private key permission from %s should be set to 400 or 600", privKeyFilepath), err.Error()) + + privKeyFilepath = filepath.Join(privKeyDir, "privkey400.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0400)) + + err = loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer", cannedPasswordRetriever) + assert.NoError(t, err) + + privKeyFilepath = filepath.Join(privKeyDir, "privkey600.pem") + assert.NoError(t, ioutil.WriteFile(privKeyFilepath, privKeyFixture, 0600)) + + err = loadPrivKeyFromPath(privKeyImporters, privKeyFilepath, "signer", cannedPasswordRetriever) + assert.NoError(t, err) +} + +var pubKeyFixture = []byte(`-----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUIH9AYtrcDFzZrFJBdJZkn21d+4c + H3nzy2O6Q/ct4BjOBKa+WCdRtPo78bA+C/7t81ADQO8Jqaj59W50rwoqDQ== + -----END PUBLIC KEY-----`) + +func TestLoadPubKeyFailure(t *testing.T) { + pubKeyDir, err := ioutil.TempDir("", "key-load-test-pubkey-") + assert.NoError(t, err) + defer os.RemoveAll(pubKeyDir) + pubKeyFilepath := filepath.Join(pubKeyDir, "pubkey.pem") + assert.NoError(t, ioutil.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms)) + keyStorageDir, err := ioutil.TempDir("", "loaded-keys-") + assert.NoError(t, err) + defer os.RemoveAll(keyStorageDir) + + passwd := "password" + cannedPasswordRetriever := passphrase.ConstantRetriever(passwd) + keyFileStore, err := storage.NewPrivateKeyFileStorage(keyStorageDir, notary.KeyExtension) + assert.NoError(t, err) + privKeyImporters := []utils.Importer{keyFileStore} + + // import the key to our keyStorageDir - it should fail + err = loadPrivKeyFromPath(privKeyImporters, pubKeyFilepath, "signer", cannedPasswordRetriever) + assert.Error(t, err) + assert.Contains(t, fmt.Sprintf("provided file %s is not a supported private key - to add a signer's public key use docker trust signer-add", pubKeyFilepath), err.Error()) +}