| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package command |
| |
| import ( |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/sha256" |
| "crypto/x509" |
| "encoding/base64" |
| "encoding/pem" |
| "errors" |
| "fmt" |
| "os" |
| "regexp" |
| "strings" |
| |
| "github.com/hashicorp/vault/api" |
| |
| "github.com/google/tink/go/kwp/subtle" |
| |
| "github.com/mitchellh/cli" |
| "github.com/posener/complete" |
| ) |
| |
| var ( |
| _ cli.Command = (*TransitImportCommand)(nil) |
| _ cli.CommandAutocomplete = (*TransitImportCommand)(nil) |
| keyPath = regexp.MustCompile("^(.*)/keys/([^/]*)$") |
| ) |
| |
| type TransitImportCommand struct { |
| *BaseCommand |
| } |
| |
| func (c *TransitImportCommand) Synopsis() string { |
| return "Import a key into the Transit secrets engines." |
| } |
| |
| func (c *TransitImportCommand) Help() string { |
| helpText := ` |
| Usage: vault transit import PATH KEY [options...] |
| |
| Using the Transit key wrapping system, imports key material from |
| the base64 encoded KEY (either directly on the CLI or via @path notation), |
| into a new key whose API path is PATH. To import a new version into an |
| existing key, use import_version. The remaining options after KEY (key=value |
| style) are passed on to the Transit create key endpoint. If your |
| system or device natively supports the RSA AES key wrap mechanism (such as |
| the PKCS#11 mechanism CKM_RSA_AES_KEY_WRAP), you should use it directly |
| rather than this command. |
| |
| ` + c.Flags().Help() |
| |
| return strings.TrimSpace(helpText) |
| } |
| |
| func (c *TransitImportCommand) Flags() *FlagSets { |
| return c.flagSet(FlagSetHTTP) |
| } |
| |
| func (c *TransitImportCommand) AutocompleteArgs() complete.Predictor { |
| return nil |
| } |
| |
| func (c *TransitImportCommand) AutocompleteFlags() complete.Flags { |
| return c.Flags().Completions() |
| } |
| |
| func (c *TransitImportCommand) Run(args []string) int { |
| return ImportKey(c.BaseCommand, "import", transitImportKeyPath, c.Flags(), args) |
| } |
| |
| func transitImportKeyPath(s string, operation string) (path string, apiPath string, err error) { |
| parts := keyPath.FindStringSubmatch(s) |
| if len(parts) != 3 { |
| return "", "", errors.New("expected transit path and key name in the form :path:/keys/:name:") |
| } |
| path = parts[1] |
| keyName := parts[2] |
| apiPath = path + "/keys/" + keyName + "/" + operation |
| |
| return path, apiPath, nil |
| } |
| |
| type ImportKeyFunc func(s string, operation string) (path string, apiPath string, err error) |
| |
| // error codes: 1: user error, 2: internal computation error, 3: remote api call error |
| func ImportKey(c *BaseCommand, operation string, pathFunc ImportKeyFunc, flags *FlagSets, args []string) int { |
| // Parse and validate the arguments. |
| if err := flags.Parse(args); err != nil { |
| c.UI.Error(err.Error()) |
| return 1 |
| } |
| |
| args = flags.Args() |
| if len(args) < 2 { |
| c.UI.Error(fmt.Sprintf("Incorrect argument count (expected 2+, got %d). Wanted PATH to import into and KEY material.", len(args))) |
| return 1 |
| } |
| |
| client, err := c.Client() |
| if err != nil { |
| c.UI.Error(err.Error()) |
| return 2 |
| } |
| |
| ephemeralAESKey := make([]byte, 32) |
| _, err = rand.Read(ephemeralAESKey) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failed to generate ephemeral key: %v", err)) |
| } |
| path, apiPath, err := pathFunc(args[0], operation) |
| if err != nil { |
| c.UI.Error(err.Error()) |
| return 1 |
| } |
| keyMaterial := args[1] |
| if keyMaterial[0] == '@' { |
| keyMaterialBytes, err := os.ReadFile(keyMaterial[1:]) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("error reading key material file: %v", err)) |
| return 1 |
| } |
| |
| keyMaterial = string(keyMaterialBytes) |
| } |
| |
| key, err := base64.StdEncoding.DecodeString(keyMaterial) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("error base64 decoding source key material: %v", err)) |
| return 1 |
| } |
| // Fetch the wrapping key |
| c.UI.Output("Retrieving wrapping key.") |
| wrappingKey, err := fetchWrappingKey(c, client, path) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failed to fetch wrapping key: %v", err)) |
| return 3 |
| } |
| c.UI.Output("Wrapping source key with ephemeral key.") |
| wrapKWP, err := subtle.NewKWP(ephemeralAESKey) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failure building key wrapping key: %v", err)) |
| return 2 |
| } |
| wrappedTargetKey, err := wrapKWP.Wrap(key) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failure wrapping source key: %v", err)) |
| return 2 |
| } |
| c.UI.Output("Encrypting ephemeral key with wrapping key.") |
| wrappedAESKey, err := rsa.EncryptOAEP( |
| sha256.New(), |
| rand.Reader, |
| wrappingKey.(*rsa.PublicKey), |
| ephemeralAESKey, |
| []byte{}, |
| ) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failure encrypting wrapped key: %v", err)) |
| return 2 |
| } |
| combinedCiphertext := append(wrappedAESKey, wrappedTargetKey...) |
| importCiphertext := base64.StdEncoding.EncodeToString(combinedCiphertext) |
| |
| // Parse all the key options |
| data, err := parseArgsData(os.Stdin, args[2:]) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("Failed to parse extra K=V data: %s", err)) |
| return 1 |
| } |
| if data == nil { |
| data = make(map[string]interface{}, 1) |
| } |
| |
| data["ciphertext"] = importCiphertext |
| |
| c.UI.Output("Submitting wrapped key.") |
| // Finally, call import |
| |
| _, err = client.Logical().Write(apiPath, data) |
| if err != nil { |
| c.UI.Error(fmt.Sprintf("failed to call import:%v", err)) |
| return 3 |
| } else { |
| c.UI.Output("Success!") |
| return 0 |
| } |
| } |
| |
| func fetchWrappingKey(c *BaseCommand, client *api.Client, path string) (any, error) { |
| resp, err := client.Logical().Read(path + "/wrapping_key") |
| if err != nil { |
| return nil, fmt.Errorf("error fetching wrapping key: %w", err) |
| } |
| if resp == nil { |
| return nil, fmt.Errorf("no mount found at %s: %v", path, err) |
| } |
| key, ok := resp.Data["public_key"] |
| if !ok { |
| c.UI.Error("could not find wrapping key") |
| } |
| keyBlock, _ := pem.Decode([]byte(key.(string))) |
| parsedKey, err := x509.ParsePKIXPublicKey(keyBlock.Bytes) |
| if err != nil { |
| return nil, fmt.Errorf("error parsing wrapping key: %w", err) |
| } |
| return parsedKey, nil |
| } |