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 }