blob: 3c10c32903d346e4cfca68187ccfab0537a0df3e [file] [log] [blame]
// 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
}