// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package pki import ( "bytes" "context" "encoding/pem" "net/http" "strings" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" ) func pathGenerateKey(b *backend) *framework.Path { return &framework.Path{ Pattern: "keys/generate/(internal|exported|kms)", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, OperationVerb: "generate", OperationSuffix: "internal-key|exported-key|kms-key", }, Fields: map[string]*framework.FieldSchema{ keyNameParam: { Type: framework.TypeString, Description: "Optional name to be used for this key", }, keyTypeParam: { Type: framework.TypeString, Default: "rsa", Description: `The type of key to use; defaults to RSA. "rsa" "ec" and "ed25519" are the only valid values.`, AllowedValues: []interface{}{"rsa", "ec", "ed25519"}, DisplayAttrs: &framework.DisplayAttributes{ Value: "rsa", }, }, keyBitsParam: { Type: framework.TypeInt, Default: 0, Description: `The number of bits to use. Allowed values are 0 (universal default); with rsa key_type: 2048 (default), 3072, or 4096; with ec key_type: 224, 256 (default), 384, or 521; ignored with ed25519.`, }, "managed_key_name": { Type: framework.TypeString, Description: `The name of the managed key to use when the exported type is kms. When kms type is the key type, this field or managed_key_id is required. Ignored for other types.`, }, "managed_key_id": { Type: framework.TypeString, Description: `The name of the managed key to use when the exported type is kms. When kms type is the key type, this field or managed_key_name is required. Ignored for other types.`, }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathGenerateKeyHandler, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "key_id": { Type: framework.TypeString, Description: `ID assigned to this key.`, Required: true, }, "key_name": { Type: framework.TypeString, Description: `Name assigned to this key.`, Required: true, }, "key_type": { Type: framework.TypeString, Description: `The type of key to use; defaults to RSA. "rsa" "ec" and "ed25519" are the only valid values.`, Required: true, }, "private_key": { Type: framework.TypeString, Description: `The private key string`, Required: false, }, }, }}, }, ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, }, HelpSynopsis: pathGenerateKeyHelpSyn, HelpDescription: pathGenerateKeyHelpDesc, } } const ( pathGenerateKeyHelpSyn = `Generate a new private key used for signing.` pathGenerateKeyHelpDesc = `This endpoint will generate a new key pair of the specified type (internal, exported, or kms).` ) func (b *backend) pathGenerateKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { // Since we're planning on updating issuers here, grab the lock so we've // got a consistent view. b.issuersLock.Lock() defer b.issuersLock.Unlock() if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Can not generate keys until migration has completed"), nil } sc := b.makeStorageContext(ctx, req.Storage) keyName, err := getKeyName(sc, data) if err != nil { // Fail Immediately if Key Name is in Use, etc... return logical.ErrorResponse(err.Error()), nil } exportPrivateKey := false var keyBundle certutil.KeyBundle var actualPrivateKeyType certutil.PrivateKeyType switch { case strings.HasSuffix(req.Path, "/exported"): exportPrivateKey = true fallthrough case strings.HasSuffix(req.Path, "/internal"): keyType := data.Get(keyTypeParam).(string) keyBits := data.Get(keyBitsParam).(int) keyBits, _, err := certutil.ValidateDefaultOrValueKeyTypeSignatureLength(keyType, keyBits, 0) if err != nil { return logical.ErrorResponse("Validation for key_type, key_bits failed: %s", err.Error()), nil } // Internal key generation, stored in storage keyBundle, err = certutil.CreateKeyBundle(keyType, keyBits, b.GetRandomReader()) if err != nil { return nil, err } actualPrivateKeyType = keyBundle.PrivateKeyType case strings.HasSuffix(req.Path, "/kms"): keyId, err := getManagedKeyId(data) if err != nil { return nil, err } keyBundle, actualPrivateKeyType, err = createKmsKeyBundle(ctx, b, keyId) if err != nil { return nil, err } default: return logical.ErrorResponse("Unknown type of key to generate"), nil } privateKeyPemString, err := keyBundle.ToPrivateKeyPemString() if err != nil { return nil, err } key, _, err := sc.importKey(privateKeyPemString, keyName, keyBundle.PrivateKeyType) if err != nil { return nil, err } responseData := map[string]interface{}{ keyIdParam: key.ID, keyNameParam: key.Name, keyTypeParam: string(actualPrivateKeyType), } if exportPrivateKey { responseData["private_key"] = privateKeyPemString } return &logical.Response{ Data: responseData, }, nil } func pathImportKey(b *backend) *framework.Path { return &framework.Path{ Pattern: "keys/import", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, OperationVerb: "import", OperationSuffix: "key", }, Fields: map[string]*framework.FieldSchema{ keyNameParam: { Type: framework.TypeString, Description: "Optional name to be used for this key", }, "pem_bundle": { Type: framework.TypeString, Description: `PEM-format, unencrypted secret key`, }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathImportKeyHandler, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "key_id": { Type: framework.TypeString, Description: `ID assigned to this key.`, Required: true, }, "key_name": { Type: framework.TypeString, Description: `Name assigned to this key.`, Required: true, }, "key_type": { Type: framework.TypeString, Description: `The type of key to use; defaults to RSA. "rsa" "ec" and "ed25519" are the only valid values.`, Required: true, }, }, }}, }, ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, }, HelpSynopsis: pathImportKeyHelpSyn, HelpDescription: pathImportKeyHelpDesc, } } const ( pathImportKeyHelpSyn = `Import the specified key.` pathImportKeyHelpDesc = `This endpoint allows importing a specified issuer key from a pem bundle. If key_name is set, that will be set on the key, assuming the key did not exist previously.` ) func (b *backend) pathImportKeyHandler(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { // Since we're planning on updating issuers here, grab the lock so we've // got a consistent view. b.issuersLock.Lock() defer b.issuersLock.Unlock() if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Cannot import keys until migration has completed"), nil } sc := b.makeStorageContext(ctx, req.Storage) pemBundle := data.Get("pem_bundle").(string) keyName, err := getKeyName(sc, data) if err != nil { return logical.ErrorResponse(err.Error()), nil } if len(pemBundle) < 64 { // It is almost nearly impossible to store a complete key in // less than 64 bytes. It is definitely impossible to do so when PEM // encoding has been applied. Detect this and give a better warning // than "provided PEM block contained no data" in this case. This is // because the PEM headers contain 5*4 + 6 + 4 + 2 + 2 = 34 characters // minimum (five dashes, "BEGIN" + space + at least one character // identifier, "END" + space + at least one character identifier, and // a pair of new lines). That would leave 30 bytes for Base64 data, // meaning at most a 22-byte DER key. Even with a 128-bit key, 6 bytes // is not sufficient for the required ASN.1 structure and OID encoding. // // However, < 64 bytes is probably a good length for a file path so // suggest that is the case. return logical.ErrorResponse("provided data for import was too short; perhaps a path was passed to the API rather than the contents of a PEM file"), nil } pemBytes := []byte(pemBundle) var pemBlock *pem.Block var keys []string for len(bytes.TrimSpace(pemBytes)) > 0 { pemBlock, pemBytes = pem.Decode(pemBytes) if pemBlock == nil { return logical.ErrorResponse("provided PEM block contained no data"), nil } pemBlockString := string(pem.EncodeToMemory(pemBlock)) keys = append(keys, pemBlockString) } if len(keys) != 1 { return logical.ErrorResponse("only a single key can be present within the pem_bundle for importing"), nil } key, existed, err := importKeyFromBytes(sc, keys[0], keyName) if err != nil { return logical.ErrorResponse(err.Error()), nil } resp := logical.Response{ Data: map[string]interface{}{ keyIdParam: key.ID, keyNameParam: key.Name, keyTypeParam: key.PrivateKeyType, }, } if existed { resp.AddWarning("Key already imported, use key/ endpoint to update name.") } return &resp, nil }