github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/trust/key_generate.go (about)

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