trust: key-load and key-generate code
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
dde9f614a7
commit
604bc3f22d
25
cli/command/trust/helpers_test.go
Normal file
25
cli/command/trust/helpers_test.go
Normal file
@ -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")
|
||||||
|
}
|
122
cli/command/trust/key_generate.go
Normal file
122
cli/command/trust/key_generate.go
Normal file
@ -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)
|
||||||
|
}
|
153
cli/command/trust/key_generate_test.go
Normal file
153
cli/command/trust/key_generate_test.go
Normal file
@ -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 ~/<trust_dir>/private/<key_id>.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)
|
||||||
|
}
|
90
cli/command/trust/key_load.go
Normal file
90
cli/command/trust/key_load.go
Normal file
@ -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)
|
||||||
|
}
|
208
cli/command/trust/key_load_test.go
Normal file
208
cli/command/trust/key_load_test.go
Normal file
@ -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 ~/<trust_dir>/private/<key_id>.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())
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user