1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
package trust
import (
"encoding/pem"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/tuf/data"
tufutils "github.com/theupdateframework/notary/tuf/utils"
)
type keyGenerateOptions struct {
name string
directory string
}
func newKeyGenerateCommand(dockerCli command.Streams) *cobra.Command {
options := keyGenerateOptions{}
cmd := &cobra.Command{
Use: "generate NAME",
Short: "Generate and load a signing key-pair",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
options.name = args[0]
return setupPassphraseAndGenerateKeys(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVar(&options.directory, "dir", "", "Directory to generate key in, defaults to current directory")
return cmd
}
// key names can use lowercase alphanumeric + _ + - characters
var validKeyName = regexp.MustCompile(`^[a-z0-9][a-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 target dir on disk
func validateKeyArgs(keyName string, targetDir string) error {
if !validKeyName(keyName) {
return fmt.Errorf("key name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", keyName)
}
pubKeyFileName := keyName + ".pub"
if _, err := os.Stat(targetDir); err != nil {
return fmt.Errorf("public key path does not exist: \"%s\"", targetDir)
}
targetPath := filepath.Join(targetDir, pubKeyFileName)
if _, err := os.Stat(targetPath); err == nil {
return fmt.Errorf("public key file already exists: \"%s\"", targetPath)
}
return nil
}
func setupPassphraseAndGenerateKeys(streams command.Streams, opts keyGenerateOptions) error {
targetDir := opts.directory
if targetDir == "" {
cwd, err := os.Getwd()
if err != nil {
return err
}
targetDir = cwd
}
return validateAndGenerateKey(streams, opts.name, targetDir)
}
func validateAndGenerateKey(streams command.Streams, keyName string, workingDir string) error {
freshPassRetGetter := func() notary.PassRetriever { return trust.GetPassphraseRetriever(streams.In(), streams.Out()) }
if err := validateKeyArgs(keyName, workingDir); err != nil {
return err
}
fmt.Fprintf(streams.Out(), "Generating key for %s...\n", keyName)
// Automatically load the private key to local storage for use
privKeyFileStore, err := trustmanager.NewKeyFileStore(trust.GetTrustDirectory(), freshPassRetGetter())
if err != nil {
return err
}
pubPEM, err := generateKeyAndOutputPubPEM(keyName, privKeyFileStore)
if err != nil {
fmt.Fprint(streams.Out(), err.Error())
return errors.Wrapf(err, "failed to generate key for %s", keyName)
}
// Output the public key to a file in the CWD or specified dir
writtenPubFile, err := writePubKeyPEMToDir(pubPEM, keyName, workingDir)
if err != nil {
return err
}
fmt.Fprintf(streams.Out(), "Successfully generated and loaded private key. Corresponding public key available: %s\n", writtenPubFile)
return nil
}
func generateKeyAndOutputPubPEM(keyName string, privKeyStore trustmanager.KeyStore) (pem.Block, error) {
privKey, err := tufutils.GenerateKey(data.ECDSAKey)
if err != nil {
return pem.Block{}, err
}
if err := privKeyStore.AddKey(trustmanager.KeyInfo{Role: data.RoleName(keyName)}, privKey); err != nil {
return pem.Block{}, err
}
pubKey := data.PublicKeyFromPrivate(privKey)
return pem.Block{
Type: "PUBLIC KEY",
Headers: map[string]string{
"role": keyName,
},
Bytes: pubKey.Public(),
}, nil
}
func writePubKeyPEMToDir(pubPEM pem.Block, keyName, workingDir string) (string, error) {
// Output the public key to a file in the CWD or specified dir
pubFileName := strings.Join([]string{keyName, "pub"}, ".")
pubFilePath := filepath.Join(workingDir, pubFileName)
if err := os.WriteFile(pubFilePath, pem.EncodeToMemory(&pubPEM), notary.PrivNoExecPerms); err != nil {
return "", errors.Wrapf(err, "failed to write public key to %s", pubFilePath)
}
return pubFilePath, nil
}
|