2017-09-26 20:16:18 -04:00
|
|
|
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"
|
2017-10-25 13:13:24 -04:00
|
|
|
"github.com/pkg/errors"
|
2017-09-26 20:16:18 -04:00
|
|
|
"github.com/spf13/cobra"
|
2017-10-30 12:21:41 -04:00
|
|
|
"github.com/theupdateframework/notary"
|
|
|
|
"github.com/theupdateframework/notary/trustmanager"
|
|
|
|
"github.com/theupdateframework/notary/tuf/data"
|
|
|
|
tufutils "github.com/theupdateframework/notary/tuf/utils"
|
2017-09-26 20:16:18 -04:00
|
|
|
)
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
type keyGenerateOptions struct {
|
|
|
|
name string
|
|
|
|
directory string
|
|
|
|
}
|
|
|
|
|
2017-09-26 20:16:18 -04:00
|
|
|
func newKeyGenerateCommand(dockerCli command.Streams) *cobra.Command {
|
2017-10-10 13:16:01 -04:00
|
|
|
options := keyGenerateOptions{}
|
2017-09-26 20:16:18 -04:00
|
|
|
cmd := &cobra.Command{
|
2017-10-25 13:45:10 -04:00
|
|
|
Use: "generate NAME",
|
2017-09-26 20:16:18 -04:00
|
|
|
Short: "Generate and load a signing key-pair",
|
2017-09-26 14:46:38 -04:00
|
|
|
Args: cli.ExactArgs(1),
|
2017-09-26 20:16:18 -04:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2017-10-10 13:16:01 -04:00
|
|
|
options.name = args[0]
|
|
|
|
return setupPassphraseAndGenerateKeys(dockerCli, options)
|
2017-09-26 20:16:18 -04:00
|
|
|
},
|
|
|
|
}
|
2017-10-10 13:16:01 -04:00
|
|
|
flags := cmd.Flags()
|
2017-10-25 13:45:10 -04:00
|
|
|
flags.StringVar(&options.directory, "dir", "", "Directory to generate key in, defaults to current directory")
|
2017-09-26 20:16:18 -04:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2017-10-25 13:45:10 -04:00
|
|
|
// key names can use lowercase alphanumeric + _ + - characters
|
|
|
|
var validKeyName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
2017-09-26 20:16:18 -04:00
|
|
|
|
|
|
|
// validate that all of the key names are unique and are alphanumeric + _ + -
|
2017-10-25 13:45:10 -04:00
|
|
|
// and that we do not already have public key files in the target dir on disk
|
|
|
|
func validateKeyArgs(keyName string, targetDir string) error {
|
2017-09-26 14:46:38 -04:00
|
|
|
if !validKeyName(keyName) {
|
2017-10-25 13:45:10 -04:00
|
|
|
return fmt.Errorf("key name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", keyName)
|
2017-09-26 14:46:38 -04:00
|
|
|
}
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2017-09-26 14:46:38 -04:00
|
|
|
pubKeyFileName := keyName + ".pub"
|
2017-10-25 13:45:10 -04:00
|
|
|
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)
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
func setupPassphraseAndGenerateKeys(streams command.Streams, opts keyGenerateOptions) error {
|
|
|
|
targetDir := opts.directory
|
|
|
|
if targetDir == "" {
|
|
|
|
cwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
targetDir = cwd
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
2017-10-10 13:16:01 -04:00
|
|
|
return validateAndGenerateKey(streams, opts.name, targetDir)
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
func validateAndGenerateKey(streams command.Streams, keyName string, workingDir string) error {
|
|
|
|
freshPassRetGetter := func() notary.PassRetriever { return trust.GetPassphraseRetriever(streams.In(), streams.Out()) }
|
2017-09-26 14:46:38 -04:00
|
|
|
if err := validateKeyArgs(keyName, workingDir); err != nil {
|
2017-09-26 20:16:18 -04:00
|
|
|
return err
|
|
|
|
}
|
2017-10-25 13:45:10 -04:00
|
|
|
fmt.Fprintf(streams.Out(), "Generating key for %s...\n", keyName)
|
2017-10-10 13:16:01 -04:00
|
|
|
// 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 {
|
2019-10-28 20:52:36 -04:00
|
|
|
fmt.Fprint(streams.Out(), err.Error())
|
2017-10-25 13:13:24 -04:00
|
|
|
return errors.Wrapf(err, "failed to generate key for %s", keyName)
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
// Output the public key to a file in the CWD or specified dir
|
|
|
|
writtenPubFile, err := writePubKeyPEMToDir(pubPEM, keyName, workingDir)
|
2017-09-26 20:16:18 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-10-10 13:16:01 -04:00
|
|
|
fmt.Fprintf(streams.Out(), "Successfully generated and loaded private key. Corresponding public key available: %s\n", writtenPubFile)
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateKeyAndOutputPubPEM(keyName string, privKeyStore trustmanager.KeyStore) (pem.Block, error) {
|
|
|
|
privKey, err := tufutils.GenerateKey(data.ECDSAKey)
|
2017-09-26 20:16:18 -04:00
|
|
|
if err != nil {
|
2017-10-10 13:16:01 -04:00
|
|
|
return pem.Block{}, err
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
|
2019-04-02 05:20:59 -04:00
|
|
|
if err := privKeyStore.AddKey(trustmanager.KeyInfo{Role: data.RoleName(keyName)}, privKey); err != nil {
|
2017-10-10 13:16:01 -04:00
|
|
|
return pem.Block{}, err
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
pubKey := data.PublicKeyFromPrivate(privKey)
|
2017-10-10 13:16:01 -04:00
|
|
|
return pem.Block{
|
2017-09-26 20:16:18 -04:00
|
|
|
Type: "PUBLIC KEY",
|
|
|
|
Headers: map[string]string{
|
|
|
|
"role": keyName,
|
|
|
|
},
|
|
|
|
Bytes: pubKey.Public(),
|
2017-10-10 13:16:01 -04:00
|
|
|
}, nil
|
|
|
|
}
|
2017-09-26 20:16:18 -04:00
|
|
|
|
2017-10-10 13:16:01 -04:00
|
|
|
func writePubKeyPEMToDir(pubPEM pem.Block, keyName, workingDir string) (string, error) {
|
|
|
|
// Output the public key to a file in the CWD or specified dir
|
2017-09-26 20:16:18 -04:00
|
|
|
pubFileName := strings.Join([]string{keyName, "pub"}, ".")
|
2017-10-10 13:16:01 -04:00
|
|
|
pubFilePath := filepath.Join(workingDir, pubFileName)
|
2022-02-25 08:33:57 -05:00
|
|
|
if err := os.WriteFile(pubFilePath, pem.EncodeToMemory(&pubPEM), notary.PrivNoExecPerms); err != nil {
|
2017-10-25 13:13:24 -04:00
|
|
|
return "", errors.Wrapf(err, "failed to write public key to %s", pubFilePath)
|
2017-10-10 13:16:01 -04:00
|
|
|
}
|
|
|
|
return pubFilePath, nil
|
2017-09-26 20:16:18 -04:00
|
|
|
}
|