github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/cli/command/trust/key_generate.go (about)

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