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 }