mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Provide public key encryption via transit engine (#17934)
* import rsa and ecdsa public keys * allow import_version to update public keys - wip * allow import_version to update public keys * move check key fields into func * put private/public keys in same switch cases * fix method in UpdateKeyVersion * move asymmetrics keys switch to its own method - WIP * test import public and update it with private counterpart * test import public keys * use public_key to encrypt if RSAKey is not present and failed to decrypt if key version does not have a private key * move key to KeyEntry parsing from Policy to KeyEntry method * move extracting of key from input fields into helper function * change back policy Import signature to keep backwards compatibility and add new method to import private or public keys * test import with imported public rsa and ecdsa keys * descriptions and error messages * error messages, remove comments and unused code * changelog * documentation - wip * suggested changes - error messages/typos and unwrap public key passed * fix unwrap key error * fail if both key fields have been set * fix in extractKeyFromFields, passing a PolicyRequest wouldn't not work * checks for read, sign and verify endpoints so they don't return errors when a private key was not imported and tests * handle panic on "export key" endpoint if imported key is public * fmt * remove 'isPrivateKey' argument from 'UpdateKeyVersion' and 'parseFromKey' methods also: rename 'UpdateKeyVersion' method to 'ImportPrivateKeyForVersion' and 'IsPublicKeyImported' to 'IsPrivateKeyMissing' * delete 'RSAPublicKey' when private key is imported * path_export: return public_key for ecdsa and rsa when there's no private key imported * allow signed data validation with pss algorithm * remove NOTE comment * fix typo in EC public key export where empty derBytes was being used * export rsa public key in pkcs8 format instead of pkcs1 and improve test * change logic on how check for is private key missing is calculated --------- Co-authored-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
		| @@ -2022,3 +2022,257 @@ func TestTransitPKICSR(t *testing.T) { | ||||
| 	t.Logf("root: %v", rootCertPEM) | ||||
| 	t.Logf("leaf: %v", leafCertPEM) | ||||
| } | ||||
|  | ||||
| func TestTransit_ReadPublicKeyImported(t *testing.T) { | ||||
| 	testTransit_ReadPublicKeyImported(t, "rsa-2048") | ||||
| 	testTransit_ReadPublicKeyImported(t, "ecdsa-p256") | ||||
| } | ||||
|  | ||||
| func testTransit_ReadPublicKeyImported(t *testing.T, keyType string) { | ||||
| 	generateKeys(t) | ||||
| 	b, s := createBackendWithStorage(t) | ||||
| 	keyID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to generate key ID: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get key | ||||
| 	privateKey := getKey(t, keyType) | ||||
| 	publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to extract the public key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Import key | ||||
| 	importReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"public_key": publicKeyBytes, | ||||
| 			"type":       keyType, | ||||
| 		}, | ||||
| 	} | ||||
| 	importResp, err := b.HandleRequest(context.Background(), importReq) | ||||
| 	if err != nil || (importResp != nil && importResp.IsError()) { | ||||
| 		t.Fatalf("failed to import public key. err: %s\nresp: %#v", err, importResp) | ||||
| 	} | ||||
|  | ||||
| 	// Read key | ||||
| 	readReq := &logical.Request{ | ||||
| 		Operation: logical.ReadOperation, | ||||
| 		Path:      "keys/" + keyID, | ||||
| 		Storage:   s, | ||||
| 	} | ||||
|  | ||||
| 	readResp, err := b.HandleRequest(context.Background(), readReq) | ||||
| 	if err != nil || (readResp != nil && readResp.IsError()) { | ||||
| 		t.Fatalf("failed to read key. err: %s\nresp: %#v", err, readResp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTransit_SignWithImportedPublicKey(t *testing.T) { | ||||
| 	testTransit_SignWithImportedPublicKey(t, "rsa-2048") | ||||
| 	testTransit_SignWithImportedPublicKey(t, "ecdsa-p256") | ||||
| } | ||||
|  | ||||
| func testTransit_SignWithImportedPublicKey(t *testing.T, keyType string) { | ||||
| 	generateKeys(t) | ||||
| 	b, s := createBackendWithStorage(t) | ||||
| 	keyID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to generate key ID: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get key | ||||
| 	privateKey := getKey(t, keyType) | ||||
| 	publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to extract the public key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Import key | ||||
| 	importReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"public_key": publicKeyBytes, | ||||
| 			"type":       keyType, | ||||
| 		}, | ||||
| 	} | ||||
| 	importResp, err := b.HandleRequest(context.Background(), importReq) | ||||
| 	if err != nil || (importResp != nil && importResp.IsError()) { | ||||
| 		t.Fatalf("failed to import public key. err: %s\nresp: %#v", err, importResp) | ||||
| 	} | ||||
|  | ||||
| 	// Sign text | ||||
| 	signReq := &logical.Request{ | ||||
| 		Path:      "sign/" + keyID, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Storage:   s, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"plaintext": base64.StdEncoding.EncodeToString([]byte(testPlaintext)), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	_, err = b.HandleRequest(context.Background(), signReq) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected error, should have failed to sign input") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTransit_VerifyWithImportedPublicKey(t *testing.T) { | ||||
| 	generateKeys(t) | ||||
| 	keyType := "rsa-2048" | ||||
| 	b, s := createBackendWithStorage(t) | ||||
| 	keyID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to generate key ID: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get key | ||||
| 	privateKey := getKey(t, keyType) | ||||
| 	publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Retrieve public wrapping key | ||||
| 	wrappingKey, err := b.getWrappingKey(context.Background(), s) | ||||
| 	if err != nil || wrappingKey == nil { | ||||
| 		t.Fatalf("failed to retrieve public wrapping key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	privWrappingKey := wrappingKey.Keys[strconv.Itoa(wrappingKey.LatestVersion)].RSAKey | ||||
| 	pubWrappingKey := &privWrappingKey.PublicKey | ||||
|  | ||||
| 	// generate ciphertext | ||||
| 	importBlob := wrapTargetKeyForImport(t, pubWrappingKey, privateKey, keyType, "SHA256") | ||||
|  | ||||
| 	// Import private key | ||||
| 	importReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"ciphertext": importBlob, | ||||
| 			"type":       keyType, | ||||
| 		}, | ||||
| 	} | ||||
| 	importResp, err := b.HandleRequest(context.Background(), importReq) | ||||
| 	if err != nil || (importResp != nil && importResp.IsError()) { | ||||
| 		t.Fatalf("failed to import key. err: %s\nresp: %#v", err, importResp) | ||||
| 	} | ||||
|  | ||||
| 	// Sign text | ||||
| 	signReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Path:      "sign/" + keyID, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"plaintext": base64.StdEncoding.EncodeToString([]byte(testPlaintext)), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	signResp, err := b.HandleRequest(context.Background(), signReq) | ||||
| 	if err != nil || (signResp != nil && signResp.IsError()) { | ||||
| 		t.Fatalf("failed to sign plaintext. err: %s\nresp: %#v", err, signResp) | ||||
| 	} | ||||
|  | ||||
| 	// Get signature | ||||
| 	signature := signResp.Data["signature"].(string) | ||||
|  | ||||
| 	// Import new key as public key | ||||
| 	importPubReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", "public-key-rsa"), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"public_key": publicKeyBytes, | ||||
| 			"type":       keyType, | ||||
| 		}, | ||||
| 	} | ||||
| 	importPubResp, err := b.HandleRequest(context.Background(), importPubReq) | ||||
| 	if err != nil || (importPubResp != nil && importPubResp.IsError()) { | ||||
| 		t.Fatalf("failed to import public key. err: %s\nresp: %#v", err, importPubResp) | ||||
| 	} | ||||
|  | ||||
| 	// Verify signed text | ||||
| 	verifyReq := &logical.Request{ | ||||
| 		Path:      "verify/public-key-rsa", | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Storage:   s, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"input":     base64.StdEncoding.EncodeToString([]byte(testPlaintext)), | ||||
| 			"signature": signature, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	verifyResp, err := b.HandleRequest(context.Background(), verifyReq) | ||||
| 	if err != nil || (importResp != nil && verifyResp.IsError()) { | ||||
| 		t.Fatalf("failed to verify signed data. err: %s\nresp: %#v", err, importResp) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTransit_ExportPublicKeyImported(t *testing.T) { | ||||
| 	testTransit_ExportPublicKeyImported(t, "rsa-2048") | ||||
| 	testTransit_ExportPublicKeyImported(t, "ecdsa-p256") | ||||
| } | ||||
|  | ||||
| func testTransit_ExportPublicKeyImported(t *testing.T, keyType string) { | ||||
| 	generateKeys(t) | ||||
| 	b, s := createBackendWithStorage(t) | ||||
| 	keyID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to generate key ID: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get key | ||||
| 	privateKey := getKey(t, keyType) | ||||
| 	publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to extract the public key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Import key | ||||
| 	importReq := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"public_key": publicKeyBytes, | ||||
| 			"type":       keyType, | ||||
| 			"exportable": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	importResp, err := b.HandleRequest(context.Background(), importReq) | ||||
| 	if err != nil || (importResp != nil && importResp.IsError()) { | ||||
| 		t.Fatalf("failed to import public key. err: %s\nresp: %#v", err, importResp) | ||||
| 	} | ||||
|  | ||||
| 	// Export key | ||||
| 	exportReq := &logical.Request{ | ||||
| 		Operation: logical.ReadOperation, | ||||
| 		Path:      fmt.Sprintf("export/signing-key/%s/latest", keyID), | ||||
| 		Storage:   s, | ||||
| 	} | ||||
|  | ||||
| 	exportResp, err := b.HandleRequest(context.Background(), exportReq) | ||||
| 	if err != nil || (exportResp != nil && exportResp.IsError()) { | ||||
| 		t.Fatalf("failed to export key. err: %v\nresp: %#v", err, exportResp) | ||||
| 	} | ||||
|  | ||||
| 	responseKeys, exist := exportResp.Data["keys"] | ||||
| 	if !exist { | ||||
| 		t.Fatal("expected response data to hold a 'keys' field") | ||||
| 	} | ||||
|  | ||||
| 	exportedKeyBytes := responseKeys.(map[string]string)["1"] | ||||
| 	exportedKeyBlock, _ := pem.Decode([]byte(exportedKeyBytes)) | ||||
| 	publicKeyBlock, _ := pem.Decode(publicKeyBytes) | ||||
|  | ||||
| 	if !reflect.DeepEqual(publicKeyBlock.Bytes, exportedKeyBlock.Bytes) { | ||||
| 		t.Fatal("exported key bytes should have matched with imported key") | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -6,12 +6,14 @@ package transit | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/sdk/helper/keysutil" | ||||
|  | ||||
| 	uuid "github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| ) | ||||
| @@ -944,3 +946,48 @@ func TestShouldWarnAboutNonceUsage(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTransit_EncryptWithRSAPublicKey(t *testing.T) { | ||||
| 	generateKeys(t) | ||||
| 	b, s := createBackendWithStorage(t) | ||||
| 	keyType := "rsa-2048" | ||||
| 	keyID, err := uuid.GenerateUUID() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to generate key ID: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get key | ||||
| 	privateKey := getKey(t, keyType) | ||||
| 	publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	// Import key | ||||
| 	req := &logical.Request{ | ||||
| 		Storage:   s, | ||||
| 		Operation: logical.UpdateOperation, | ||||
| 		Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"public_key": publicKeyBytes, | ||||
| 			"type":       keyType, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("failed to import public key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	req = &logical.Request{ | ||||
| 		Operation: logical.CreateOperation, | ||||
| 		Path:      fmt.Sprintf("encrypt/%s", keyID), | ||||
| 		Storage:   s, | ||||
| 		Data: map[string]interface{}{ | ||||
| 			"plaintext": "bXkgc2VjcmV0IGRhdGE=", | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err = b.HandleRequest(context.Background(), req) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import ( | ||||
| 	"context" | ||||
| 	"crypto/ecdsa" | ||||
| 	"crypto/elliptic" | ||||
| 	"crypto/rsa" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/pem" | ||||
| @@ -169,7 +168,11 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st | ||||
| 			return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil | ||||
|  | ||||
| 		case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096: | ||||
| 			return encodeRSAPrivateKey(key.RSAKey), nil | ||||
| 			rsaKey, err := encodeRSAPrivateKey(key) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			return rsaKey, nil | ||||
| 		} | ||||
|  | ||||
| 	case exportTypeSigningKey: | ||||
| @@ -194,23 +197,41 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st | ||||
| 			return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil | ||||
|  | ||||
| 		case keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096: | ||||
| 			return encodeRSAPrivateKey(key.RSAKey), nil | ||||
| 			rsaKey, err := encodeRSAPrivateKey(key) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			return rsaKey, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return "", fmt.Errorf("unknown key type %v", policy.Type) | ||||
| } | ||||
|  | ||||
| func encodeRSAPrivateKey(key *rsa.PrivateKey) string { | ||||
| func encodeRSAPrivateKey(key *keysutil.KeyEntry) (string, error) { | ||||
| 	// When encoding PKCS1, the PEM header should be `RSA PRIVATE KEY`. When Go | ||||
| 	// has PKCS8 encoding support, we may want to change this. | ||||
| 	derBytes := x509.MarshalPKCS1PrivateKey(key) | ||||
| 	pemBlock := &pem.Block{ | ||||
| 		Type:  "RSA PRIVATE KEY", | ||||
| 	var blockType string | ||||
| 	var derBytes []byte | ||||
| 	var err error | ||||
| 	if !key.IsPrivateKeyMissing() { | ||||
| 		blockType = "RSA PRIVATE KEY" | ||||
| 		derBytes = x509.MarshalPKCS1PrivateKey(key.RSAKey) | ||||
| 	} else { | ||||
| 		blockType = "PUBLIC KEY" | ||||
| 		derBytes, err = x509.MarshalPKIXPublicKey(key.RSAPublicKey) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pemBlock := pem.Block{ | ||||
| 		Type:  blockType, | ||||
| 		Bytes: derBytes, | ||||
| 	} | ||||
| 	pemBytes := pem.EncodeToMemory(pemBlock) | ||||
| 	return string(pemBytes) | ||||
|  | ||||
| 	pemBytes := pem.EncodeToMemory(&pemBlock) | ||||
| 	return string(pemBytes), nil | ||||
| } | ||||
|  | ||||
| func keyEntryToECPrivateKey(k *keysutil.KeyEntry, curve elliptic.Curve) (string, error) { | ||||
| @@ -218,27 +239,46 @@ func keyEntryToECPrivateKey(k *keysutil.KeyEntry, curve elliptic.Curve) (string, | ||||
| 		return "", errors.New("nil KeyEntry provided") | ||||
| 	} | ||||
|  | ||||
| 	privKey := &ecdsa.PrivateKey{ | ||||
| 		PublicKey: ecdsa.PublicKey{ | ||||
| 			Curve: curve, | ||||
| 			X:     k.EC_X, | ||||
| 			Y:     k.EC_Y, | ||||
| 		}, | ||||
| 		D: k.EC_D, | ||||
| 	} | ||||
| 	ecder, err := x509.MarshalECPrivateKey(privKey) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	if ecder == nil { | ||||
| 		return "", errors.New("no data returned when marshalling to private key") | ||||
| 	pubKey := ecdsa.PublicKey{ | ||||
| 		Curve: curve, | ||||
| 		X:     k.EC_X, | ||||
| 		Y:     k.EC_Y, | ||||
| 	} | ||||
|  | ||||
| 	block := pem.Block{ | ||||
| 		Type:  "EC PRIVATE KEY", | ||||
| 		Bytes: ecder, | ||||
| 	var blockType string | ||||
| 	var derBytes []byte | ||||
| 	var err error | ||||
| 	if !k.IsPrivateKeyMissing() { | ||||
| 		blockType = "EC PRIVATE KEY" | ||||
| 		privKey := &ecdsa.PrivateKey{ | ||||
| 			PublicKey: pubKey, | ||||
| 			D:         k.EC_D, | ||||
| 		} | ||||
| 		derBytes, err = x509.MarshalECPrivateKey(privKey) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if derBytes == nil { | ||||
| 			return "", errors.New("no data returned when marshalling to private key") | ||||
| 		} | ||||
| 	} else { | ||||
| 		blockType = "PUBLIC KEY" | ||||
| 		derBytes, err = x509.MarshalPKIXPublicKey(&pubKey) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		if derBytes == nil { | ||||
| 			return "", errors.New("no data returned when marshalling to public key") | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.TrimSpace(string(pem.EncodeToMemory(&block))), nil | ||||
|  | ||||
| 	pemBlock := pem.Block{ | ||||
| 		Type:  blockType, | ||||
| 		Bytes: derBytes, | ||||
| 	} | ||||
|  | ||||
| 	return strings.TrimSpace(string(pem.EncodeToMemory(&pemBlock))), nil | ||||
| } | ||||
|  | ||||
| const pathExportHelpSyn = `Export named encryption or signing key` | ||||
|   | ||||
| @@ -59,6 +59,10 @@ ephemeral AES key. Can be one of "SHA1", "SHA224", "SHA256" (default), "SHA384", | ||||
| 				Description: `The base64-encoded ciphertext of the keys. The AES key should be encrypted using OAEP  | ||||
| with the wrapping key and then concatenated with the import key, wrapped by the AES key.`, | ||||
| 			}, | ||||
| 			"public_key": { | ||||
| 				Type:        framework.TypeString, | ||||
| 				Description: `The plaintext PEM public key to be imported. If "ciphertext" is set, this field is ignored.`, | ||||
| 			}, | ||||
| 			"allow_rotation": { | ||||
| 				Type:        framework.TypeBool, | ||||
| 				Description: "True if the imported key may be rotated within Vault; false otherwise.", | ||||
| @@ -128,12 +132,27 @@ func (b *backend) pathImportVersion() *framework.Path { | ||||
| 				Description: `The base64-encoded ciphertext of the keys. The AES key should be encrypted using OAEP  | ||||
| with the wrapping key and then concatenated with the import key, wrapped by the AES key.`, | ||||
| 			}, | ||||
| 			"public_key": { | ||||
| 				Type:        framework.TypeString, | ||||
| 				Description: `The plaintext public key to be imported. If "ciphertext" is set, this field is ignored.`, | ||||
| 			}, | ||||
| 			"hash_function": { | ||||
| 				Type:    framework.TypeString, | ||||
| 				Default: "SHA256", | ||||
| 				Description: `The hash function used as a random oracle in the OAEP wrapping of the user-generated, | ||||
| ephemeral AES key. Can be one of "SHA1", "SHA224", "SHA256" (default), "SHA384", or "SHA512"`, | ||||
| 			}, | ||||
| 			"bump_version": { | ||||
| 				Type:    framework.TypeBool, | ||||
| 				Default: true, | ||||
| 				Description: `By default, each operation will create a new key version. | ||||
| If set to 'false', will try to update the 'Latest' version of the key, unless changed in the "version" field.`, | ||||
| 			}, | ||||
| 			"version": { | ||||
| 				Type: framework.TypeInt, | ||||
| 				Description: `Key version to be updated, if left empty 'Latest' version will be updated. | ||||
| If "bump_version" is set to 'true', this field is ignored.`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Callbacks: map[logical.Operation]framework.OperationFunc{ | ||||
| 			logical.UpdateOperation: b.pathImportVersionWrite, | ||||
| @@ -147,11 +166,9 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
| 	name := d.Get("name").(string) | ||||
| 	derived := d.Get("derived").(bool) | ||||
| 	keyType := d.Get("type").(string) | ||||
| 	hashFnStr := d.Get("hash_function").(string) | ||||
| 	exportable := d.Get("exportable").(bool) | ||||
| 	allowPlaintextBackup := d.Get("allow_plaintext_backup").(bool) | ||||
| 	autoRotatePeriod := time.Second * time.Duration(d.Get("auto_rotate_period").(int)) | ||||
| 	ciphertextString := d.Get("ciphertext").(string) | ||||
| 	allowRotation := d.Get("allow_rotation").(bool) | ||||
|  | ||||
| 	// Ensure the caller didn't supply "convergent_encryption" as a field, since it's not supported on import. | ||||
| @@ -163,6 +180,12 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
| 		return nil, errors.New("allow_rotation must be set to true if auto-rotation is enabled") | ||||
| 	} | ||||
|  | ||||
| 	// Ensure that at least on `key` field has been set | ||||
| 	isCiphertextSet, err := checkKeyFieldsSet(d) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	polReq := keysutil.PolicyRequest{ | ||||
| 		Storage:                  req.Storage, | ||||
| 		Name:                     name, | ||||
| @@ -171,6 +194,7 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
| 		AllowPlaintextBackup:     allowPlaintextBackup, | ||||
| 		AutoRotatePeriod:         autoRotatePeriod, | ||||
| 		AllowImportedKeyRotation: allowRotation, | ||||
| 		IsPrivateKey:             isCiphertextSet, | ||||
| 	} | ||||
|  | ||||
| 	switch strings.ToLower(keyType) { | ||||
| @@ -200,11 +224,6 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
| 		return logical.ErrorResponse(fmt.Sprintf("unknown key type: %v", keyType)), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	hashFn, err := parseHashFn(hashFnStr) | ||||
| 	if err != nil { | ||||
| 		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	p, _, err := b.GetPolicy(ctx, polReq, b.GetRandomReader()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -217,14 +236,9 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
| 		return nil, errors.New("the import path cannot be used with an existing key; use import-version to rotate an existing imported key") | ||||
| 	} | ||||
|  | ||||
| 	ciphertext, err := base64.StdEncoding.DecodeString(ciphertextString) | ||||
| 	key, resp, err := b.extractKeyFromFields(ctx, req, d, polReq.KeyType, isCiphertextSet) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	key, err := b.decryptImportedKey(ctx, req.Storage, ciphertext, hashFn) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return resp, err | ||||
| 	} | ||||
|  | ||||
| 	err = b.lm.ImportPolicy(ctx, polReq, key, b.GetRandomReader()) | ||||
| @@ -237,20 +251,19 @@ func (b *backend) pathImportWrite(ctx context.Context, req *logical.Request, d * | ||||
|  | ||||
| func (b *backend) pathImportVersionWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { | ||||
| 	name := d.Get("name").(string) | ||||
| 	hashFnStr := d.Get("hash_function").(string) | ||||
| 	ciphertextString := d.Get("ciphertext").(string) | ||||
| 	bumpVersion := d.Get("bump_version").(bool) | ||||
|  | ||||
| 	isCiphertextSet, err := checkKeyFieldsSet(d) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	polReq := keysutil.PolicyRequest{ | ||||
| 		Storage: req.Storage, | ||||
| 		Name:    name, | ||||
| 		Upsert:  false, | ||||
| 		Storage:      req.Storage, | ||||
| 		Name:         name, | ||||
| 		Upsert:       false, | ||||
| 		IsPrivateKey: isCiphertextSet, | ||||
| 	} | ||||
|  | ||||
| 	hashFn, err := parseHashFn(hashFnStr) | ||||
| 	if err != nil { | ||||
| 		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||||
| 	} | ||||
|  | ||||
| 	p, _, err := b.GetPolicy(ctx, polReq, b.GetRandomReader()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -270,15 +283,26 @@ func (b *backend) pathImportVersionWrite(ctx context.Context, req *logical.Reque | ||||
| 	} | ||||
| 	defer p.Unlock() | ||||
|  | ||||
| 	ciphertext, err := base64.StdEncoding.DecodeString(ciphertextString) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	// Get param version if set else LatestVersion | ||||
| 	versionToUpdate := p.LatestVersion | ||||
| 	if version, ok := d.Raw["version"]; ok { | ||||
| 		versionToUpdate = version.(int) | ||||
| 	} | ||||
| 	importKey, err := b.decryptImportedKey(ctx, req.Storage, ciphertext, hashFn) | ||||
|  | ||||
| 	key, resp, err := b.extractKeyFromFields(ctx, req, d, p.Type, isCiphertextSet) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return resp, err | ||||
| 	} | ||||
|  | ||||
| 	if bumpVersion { | ||||
| 		err = p.ImportPublicOrPrivate(ctx, req.Storage, key, isCiphertextSet, b.GetRandomReader()) | ||||
| 	} else { | ||||
| 		// Check if given version can be updated given input | ||||
| 		err := p.KeyVersionCanBeUpdated(versionToUpdate, isCiphertextSet) | ||||
| 		if err == nil { | ||||
| 			err = p.ImportPrivateKeyForVersion(ctx, req.Storage, versionToUpdate, key) | ||||
| 		} | ||||
| 	} | ||||
| 	err = p.Import(ctx, req.Storage, importKey, b.GetRandomReader()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -336,6 +360,36 @@ func (b *backend) decryptImportedKey(ctx context.Context, storage logical.Storag | ||||
| 	return importKey, nil | ||||
| } | ||||
|  | ||||
| func (b *backend) extractKeyFromFields(ctx context.Context, req *logical.Request, d *framework.FieldData, keyType keysutil.KeyType, isPrivateKey bool) ([]byte, *logical.Response, error) { | ||||
| 	var key []byte | ||||
| 	if isPrivateKey { | ||||
| 		hashFnStr := d.Get("hash_function").(string) | ||||
| 		hashFn, err := parseHashFn(hashFnStr) | ||||
| 		if err != nil { | ||||
| 			return key, logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest | ||||
| 		} | ||||
|  | ||||
| 		ciphertextString := d.Get("ciphertext").(string) | ||||
| 		ciphertext, err := base64.StdEncoding.DecodeString(ciphertextString) | ||||
| 		if err != nil { | ||||
| 			return key, nil, err | ||||
| 		} | ||||
|  | ||||
| 		key, err = b.decryptImportedKey(ctx, req.Storage, ciphertext, hashFn) | ||||
| 		if err != nil { | ||||
| 			return key, nil, err | ||||
| 		} | ||||
| 	} else { | ||||
| 		publicKeyString := d.Get("public_key").(string) | ||||
| 		if !keyType.ImportPublicKeySupported() { | ||||
| 			return key, nil, errors.New("provided type does not support public_key import") | ||||
| 		} | ||||
| 		key = []byte(publicKeyString) | ||||
| 	} | ||||
|  | ||||
| 	return key, nil, nil | ||||
| } | ||||
|  | ||||
| func parseHashFn(hashFn string) (hash.Hash, error) { | ||||
| 	switch strings.ToUpper(hashFn) { | ||||
| 	case "SHA1": | ||||
| @@ -353,6 +407,29 @@ func parseHashFn(hashFn string) (hash.Hash, error) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // checkKeyFieldsSet: Checks which key fields are set. If both are set, an error is returned | ||||
| func checkKeyFieldsSet(d *framework.FieldData) (bool, error) { | ||||
| 	ciphertextSet := isFieldSet("ciphertext", d) | ||||
| 	publicKeySet := isFieldSet("publicKey", d) | ||||
|  | ||||
| 	if ciphertextSet && publicKeySet { | ||||
| 		return false, errors.New("only one of the following fields, ciphertext and public_key, can be set") | ||||
| 	} else if ciphertextSet { | ||||
| 		return true, nil | ||||
| 	} else { | ||||
| 		return false, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isFieldSet(fieldName string, d *framework.FieldData) bool { | ||||
| 	_, fieldSet := d.Raw[fieldName] | ||||
| 	if !fieldSet { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	pathImportWriteSyn  = "Imports an externally-generated key into a new transit key" | ||||
| 	pathImportWriteDesc = "This path is used to import an externally-generated " + | ||||
|   | ||||
| @@ -5,6 +5,7 @@ package transit | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto" | ||||
| 	"crypto/ecdsa" | ||||
| 	"crypto/ed25519" | ||||
| 	"crypto/elliptic" | ||||
| @@ -12,6 +13,7 @@ import ( | ||||
| 	"crypto/rsa" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/pem" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| @@ -427,6 +429,70 @@ func TestTransit_Import(t *testing.T) { | ||||
| 			} | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
| 	t.Run( | ||||
| 		"import public key ed25519", | ||||
| 		func(t *testing.T) { | ||||
| 			keyType := "ed25519" | ||||
| 			keyID, err := uuid.GenerateUUID() | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to generate key ID: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Get keys | ||||
| 			privateKey := getKey(t, keyType) | ||||
| 			publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			// Import key | ||||
| 			req := &logical.Request{ | ||||
| 				Storage:   s, | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 				Data: map[string]interface{}{ | ||||
| 					"public_key": publicKeyBytes, | ||||
| 					"type":       keyType, | ||||
| 				}, | ||||
| 			} | ||||
| 			_, err = b.HandleRequest(context.Background(), req) | ||||
| 			if err == nil { | ||||
| 				t.Fatalf("invalid public_key import incorrectly succeeeded") | ||||
| 			} | ||||
| 		}) | ||||
|  | ||||
| 	t.Run( | ||||
| 		"import public key ecdsa", | ||||
| 		func(t *testing.T) { | ||||
| 			keyType := "ecdsa-p256" | ||||
| 			keyID, err := uuid.GenerateUUID() | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to generate key ID: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Get keys | ||||
| 			privateKey := getKey(t, keyType) | ||||
| 			publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			// Import key | ||||
| 			req := &logical.Request{ | ||||
| 				Storage:   s, | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 				Data: map[string]interface{}{ | ||||
| 					"public_key": publicKeyBytes, | ||||
| 					"type":       keyType, | ||||
| 				}, | ||||
| 			} | ||||
| 			_, err = b.HandleRequest(context.Background(), req) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to import public key: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| func TestTransit_ImportVersion(t *testing.T) { | ||||
| @@ -573,6 +639,53 @@ func TestTransit_ImportVersion(t *testing.T) { | ||||
| 			} | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
| 	t.Run( | ||||
| 		"import rsa public key and update version with private counterpart", | ||||
| 		func(t *testing.T) { | ||||
| 			keyType := "rsa-2048" | ||||
| 			keyID, err := uuid.GenerateUUID() | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to generate key ID: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Get keys | ||||
| 			privateKey := getKey(t, keyType) | ||||
| 			importBlob := wrapTargetKeyForImport(t, pubWrappingKey, privateKey, keyType, "SHA256") | ||||
| 			publicKeyBytes, err := getPublicKey(privateKey, keyType) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			// Import RSA public key | ||||
| 			req := &logical.Request{ | ||||
| 				Storage:   s, | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      fmt.Sprintf("keys/%s/import", keyID), | ||||
| 				Data: map[string]interface{}{ | ||||
| 					"public_key": publicKeyBytes, | ||||
| 					"type":       keyType, | ||||
| 				}, | ||||
| 			} | ||||
| 			_, err = b.HandleRequest(context.Background(), req) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to import public key: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Update version - import RSA private key | ||||
| 			req = &logical.Request{ | ||||
| 				Storage:   s, | ||||
| 				Operation: logical.UpdateOperation, | ||||
| 				Path:      fmt.Sprintf("keys/%s/import_version", keyID), | ||||
| 				Data: map[string]interface{}{ | ||||
| 					"ciphertext": importBlob, | ||||
| 				}, | ||||
| 			} | ||||
| 			_, err = b.HandleRequest(context.Background(), req) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to update key: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| func wrapTargetKeyForImport(t *testing.T, wrappingKey *rsa.PublicKey, targetKey interface{}, targetKeyType string, hashFnName string) string { | ||||
| @@ -663,3 +776,40 @@ func generateKey(keyType string) (interface{}, error) { | ||||
| 		return nil, fmt.Errorf("failed to generate unsupported key type: %s", keyType) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func getPublicKey(privateKey crypto.PrivateKey, keyType string) ([]byte, error) { | ||||
| 	var publicKey crypto.PublicKey | ||||
| 	var publicKeyBytes []byte | ||||
| 	switch keyType { | ||||
| 	case "rsa-2048", "rsa-3072", "rsa-4096": | ||||
| 		publicKey = privateKey.(*rsa.PrivateKey).Public() | ||||
| 	case "ecdsa-p256", "ecdsa-p384", "ecdsa-p521": | ||||
| 		publicKey = privateKey.(*ecdsa.PrivateKey).Public() | ||||
| 	case "ed25519": | ||||
| 		publicKey = privateKey.(ed25519.PrivateKey).Public() | ||||
| 	default: | ||||
| 		return publicKeyBytes, fmt.Errorf("failed to get public key from %s key", keyType) | ||||
| 	} | ||||
|  | ||||
| 	publicKeyBytes, err := publicKeyToBytes(publicKey) | ||||
| 	if err != nil { | ||||
| 		return publicKeyBytes, err | ||||
| 	} | ||||
|  | ||||
| 	return publicKeyBytes, nil | ||||
| } | ||||
|  | ||||
| func publicKeyToBytes(publicKey crypto.PublicKey) ([]byte, error) { | ||||
| 	var publicKeyBytesPem []byte | ||||
| 	publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) | ||||
| 	if err != nil { | ||||
| 		return publicKeyBytesPem, fmt.Errorf("failed to marshal public key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	pemBlock := &pem.Block{ | ||||
| 		Type:  "PUBLIC KEY", | ||||
| 		Bytes: publicKeyBytes, | ||||
| 	} | ||||
|  | ||||
| 	return pem.EncodeToMemory(pemBlock), nil | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ package transit | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto" | ||||
| 	"crypto/elliptic" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/base64" | ||||
| @@ -408,9 +409,15 @@ func (b *backend) pathPolicyRead(ctx context.Context, req *logical.Request, d *f | ||||
| 					key.Name = "rsa-4096" | ||||
| 				} | ||||
|  | ||||
| 				var publicKey crypto.PublicKey | ||||
| 				publicKey = v.RSAPublicKey | ||||
| 				if !v.IsPrivateKeyMissing() { | ||||
| 					publicKey = v.RSAKey.Public() | ||||
| 				} | ||||
|  | ||||
| 				// Encode the RSA public key in PEM format to return over the | ||||
| 				// API | ||||
| 				derBytes, err := x509.MarshalPKIXPublicKey(v.RSAKey.Public()) | ||||
| 				derBytes, err := x509.MarshalPKIXPublicKey(publicKey) | ||||
| 				if err != nil { | ||||
| 					return nil, fmt.Errorf("error marshaling RSA public key: %w", err) | ||||
| 				} | ||||
|   | ||||
							
								
								
									
										3
									
								
								changelog/17934.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/17934.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:improvement | ||||
| secrets/transit: Add support to import public keys in transit engine and allow encryption and verification of signed data | ||||
| ``` | ||||
| @@ -63,6 +63,9 @@ type PolicyRequest struct { | ||||
| 	// AllowImportedKeyRotation indicates whether an imported key may be rotated by Vault | ||||
| 	AllowImportedKeyRotation bool | ||||
|  | ||||
| 	// Indicates whether a private or public key is imported/upserted | ||||
| 	IsPrivateKey bool | ||||
|  | ||||
| 	// The UUID of the managed key, if using one | ||||
| 	ManagedKeyUUID string | ||||
| } | ||||
| @@ -511,7 +514,7 @@ func (lm *LockManager) ImportPolicy(ctx context.Context, req PolicyRequest, key | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = p.Import(ctx, req.Storage, key, rand) | ||||
| 	err = p.ImportPublicOrPrivate(ctx, req.Storage, key, req.IsPrivateKey, rand) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error importing key: %s", err) | ||||
| 	} | ||||
|   | ||||
| @@ -167,6 +167,14 @@ func (kt KeyType) AssociatedDataSupported() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (kt KeyType) ImportPublicKeySupported() bool { | ||||
| 	switch kt { | ||||
| 	case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096, KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521: | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (kt KeyType) String() string { | ||||
| 	switch kt { | ||||
| 	case KeyType_AES128_GCM96: | ||||
| @@ -218,7 +226,8 @@ type KeyEntry struct { | ||||
| 	EC_Y *big.Int `json:"ec_y"` | ||||
| 	EC_D *big.Int `json:"ec_d"` | ||||
|  | ||||
| 	RSAKey *rsa.PrivateKey `json:"rsa_key"` | ||||
| 	RSAKey       *rsa.PrivateKey `json:"rsa_key"` | ||||
| 	RSAPublicKey *rsa.PublicKey  `json:"rsa_public_key"` | ||||
|  | ||||
| 	// The public key in an appropriate format for the type of key | ||||
| 	FormattedPublicKey string `json:"public_key"` | ||||
| @@ -234,6 +243,14 @@ type KeyEntry struct { | ||||
| 	ManagedKeyUUID string `json:"managed_key_id,omitempty"` | ||||
| } | ||||
|  | ||||
| func (ke *KeyEntry) IsPrivateKeyMissing() bool { | ||||
| 	if ke.RSAKey != nil || ke.EC_D != nil || len(ke.Key) != 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // deprecatedKeyEntryMap is used to allow JSON marshal/unmarshal | ||||
| type deprecatedKeyEntryMap map[int]KeyEntry | ||||
|  | ||||
| @@ -969,6 +986,9 @@ func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factori | ||||
| 			return "", err | ||||
| 		} | ||||
| 		key := keyEntry.RSAKey | ||||
| 		if key == nil { | ||||
| 			return "", errutil.InternalError{Err: fmt.Sprintf("cannot decrypt ciphertext, key version does not have a private counterpart")} | ||||
| 		} | ||||
| 		plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil) | ||||
| 		if err != nil { | ||||
| 			return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA decrypt the ciphertext: %v", err)} | ||||
| @@ -1043,13 +1063,13 @@ func (p *Policy) minRSAPSSSaltLength() int { | ||||
| 	return rsa.PSSSaltLengthEqualsHash | ||||
| } | ||||
|  | ||||
| func (p *Policy) maxRSAPSSSaltLength(priv *rsa.PrivateKey, hash crypto.Hash) int { | ||||
| func (p *Policy) maxRSAPSSSaltLength(keyBitLen int, hash crypto.Hash) int { | ||||
| 	// https://cs.opensource.google/go/go/+/refs/tags/go1.19:src/crypto/rsa/pss.go;l=288 | ||||
| 	return (priv.N.BitLen()-1+7)/8 - 2 - hash.Size() | ||||
| 	return (keyBitLen-1+7)/8 - 2 - hash.Size() | ||||
| } | ||||
|  | ||||
| func (p *Policy) validRSAPSSSaltLength(priv *rsa.PrivateKey, hash crypto.Hash, saltLength int) bool { | ||||
| 	return p.minRSAPSSSaltLength() <= saltLength && saltLength <= p.maxRSAPSSSaltLength(priv, hash) | ||||
| func (p *Policy) validRSAPSSSaltLength(keyBitLen int, hash crypto.Hash, saltLength int) bool { | ||||
| 	return p.minRSAPSSSaltLength() <= saltLength && saltLength <= p.maxRSAPSSSaltLength(keyBitLen, hash) | ||||
| } | ||||
|  | ||||
| func (p *Policy) SignWithOptions(ver int, context, input []byte, options *SigningOptions) (*SigningResult, error) { | ||||
| @@ -1076,6 +1096,11 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Before signing, check if key has its private part, if not return error | ||||
| 	if keyParams.IsPrivateKeyMissing() { | ||||
| 		return nil, errutil.UserError{Err: "requested version for signing does not contain a private part"} | ||||
| 	} | ||||
|  | ||||
| 	hashAlgorithm := options.HashAlgorithm | ||||
| 	marshaling := options.Marshaling | ||||
| 	saltLength := options.SaltLength | ||||
| @@ -1182,7 +1207,7 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin | ||||
|  | ||||
| 		switch sigAlgorithm { | ||||
| 		case "pss": | ||||
| 			if !p.validRSAPSSSaltLength(key, algo, saltLength) { | ||||
| 			if !p.validRSAPSSSaltLength(key.N.BitLen(), algo, saltLength) { | ||||
| 				return nil, errutil.UserError{Err: fmt.Sprintf("requested salt length %d is invalid", saltLength)} | ||||
| 			} | ||||
| 			sig, err = rsa.SignPSS(rand.Reader, key, algo, input, &rsa.PSSOptions{SaltLength: saltLength}) | ||||
| @@ -1357,8 +1382,6 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o | ||||
| 			return false, err | ||||
| 		} | ||||
|  | ||||
| 		key := keyEntry.RSAKey | ||||
|  | ||||
| 		algo, ok := CryptoHashMap[hashAlgorithm] | ||||
| 		if !ok { | ||||
| 			return false, errutil.InternalError{Err: "unsupported hash algorithm"} | ||||
| @@ -1370,12 +1393,20 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o | ||||
|  | ||||
| 		switch sigAlgorithm { | ||||
| 		case "pss": | ||||
| 			if !p.validRSAPSSSaltLength(key, algo, saltLength) { | ||||
| 			publicKey := keyEntry.RSAPublicKey | ||||
| 			if !keyEntry.IsPrivateKeyMissing() { | ||||
| 				publicKey = &keyEntry.RSAKey.PublicKey | ||||
| 			} | ||||
| 			if !p.validRSAPSSSaltLength(publicKey.N.BitLen(), algo, saltLength) { | ||||
| 				return false, errutil.UserError{Err: fmt.Sprintf("requested salt length %d is invalid", saltLength)} | ||||
| 			} | ||||
| 			err = rsa.VerifyPSS(&key.PublicKey, algo, input, sigBytes, &rsa.PSSOptions{SaltLength: saltLength}) | ||||
| 			err = rsa.VerifyPSS(publicKey, algo, input, sigBytes, &rsa.PSSOptions{SaltLength: saltLength}) | ||||
| 		case "pkcs1v15": | ||||
| 			err = rsa.VerifyPKCS1v15(&key.PublicKey, algo, input, sigBytes) | ||||
| 			publicKey := keyEntry.RSAPublicKey | ||||
| 			if !keyEntry.IsPrivateKeyMissing() { | ||||
| 				publicKey = &keyEntry.RSAKey.PublicKey | ||||
| 			} | ||||
| 			err = rsa.VerifyPKCS1v15(publicKey, algo, input, sigBytes) | ||||
| 		default: | ||||
| 			return false, errutil.InternalError{Err: fmt.Sprintf("unsupported rsa signature algorithm %s", sigAlgorithm)} | ||||
| 		} | ||||
| @@ -1396,6 +1427,10 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o | ||||
| } | ||||
|  | ||||
| func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte, randReader io.Reader) error { | ||||
| 	return p.ImportPublicOrPrivate(ctx, storage, key, true, randReader) | ||||
| } | ||||
|  | ||||
| func (p *Policy) ImportPublicOrPrivate(ctx context.Context, storage logical.Storage, key []byte, isPrivateKey bool, randReader io.Reader) error { | ||||
| 	now := time.Now() | ||||
| 	entry := KeyEntry{ | ||||
| 		CreationTime:           now, | ||||
| @@ -1422,91 +1457,42 @@ func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte | ||||
| 			p.KeySize = len(key) | ||||
| 		} | ||||
| 	} else { | ||||
| 		parsedPrivateKey, err := x509.ParsePKCS8PrivateKey(key) | ||||
| 		if err != nil { | ||||
| 			if strings.Contains(err.Error(), "unknown elliptic curve") { | ||||
| 				var edErr error | ||||
| 				parsedPrivateKey, edErr = ParsePKCS8Ed25519PrivateKey(key) | ||||
| 				if edErr != nil { | ||||
| 					return fmt.Errorf("error parsing asymmetric key:\n - assuming contents are an ed25519 private key: %v\n - original error: %w", edErr, err) | ||||
| 				} | ||||
| 		var parsedKey any | ||||
| 		var err error | ||||
| 		if isPrivateKey { | ||||
| 			parsedKey, err = x509.ParsePKCS8PrivateKey(key) | ||||
| 			if err != nil { | ||||
| 				if strings.Contains(err.Error(), "unknown elliptic curve") { | ||||
| 					var edErr error | ||||
| 					parsedKey, edErr = ParsePKCS8Ed25519PrivateKey(key) | ||||
| 					if edErr != nil { | ||||
| 						return fmt.Errorf("error parsing asymmetric key:\n - assuming contents are an ed25519 private key: %s\n - original error: %v", edErr, err) | ||||
| 					} | ||||
|  | ||||
| 				// Parsing as Ed25519-in-PKCS8-ECPrivateKey succeeded! | ||||
| 			} else if strings.Contains(err.Error(), oidSignatureRSAPSS.String()) { | ||||
| 				var rsaErr error | ||||
| 				parsedPrivateKey, rsaErr = ParsePKCS8RSAPSSPrivateKey(key) | ||||
| 				if rsaErr != nil { | ||||
| 					return fmt.Errorf("error parsing asymmetric key:\n - assuming contents are an RSA/PSS private key: %v\n - original error: %w", rsaErr, err) | ||||
| 				} | ||||
| 					// Parsing as Ed25519-in-PKCS8-ECPrivateKey succeeded! | ||||
| 				} else if strings.Contains(err.Error(), oidSignatureRSAPSS.String()) { | ||||
| 					var rsaErr error | ||||
| 					parsedKey, rsaErr = ParsePKCS8RSAPSSPrivateKey(key) | ||||
| 					if rsaErr != nil { | ||||
| 						return fmt.Errorf("error parsing asymmetric key:\n - assuming contents are an RSA/PSS private key: %v\n - original error: %w", rsaErr, err) | ||||
| 					} | ||||
|  | ||||
| 				// Parsing as RSA-PSS in PKCS8 succeeded! | ||||
| 			} else { | ||||
| 				return fmt.Errorf("error parsing asymmetric key: %s", err) | ||||
| 					// Parsing as RSA-PSS in PKCS8 succeeded! | ||||
| 				} else { | ||||
| 					return fmt.Errorf("error parsing asymmetric key: %s", err) | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			pemBlock, _ := pem.Decode(key) | ||||
| 			parsedKey, err = x509.ParsePKIXPublicKey(pemBlock.Bytes) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("error parsing public key: %s", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		switch parsedPrivateKey.(type) { | ||||
| 		case *ecdsa.PrivateKey: | ||||
| 			if p.Type != KeyType_ECDSA_P256 && p.Type != KeyType_ECDSA_P384 && p.Type != KeyType_ECDSA_P521 { | ||||
| 				return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey) | ||||
| 			} | ||||
|  | ||||
| 			ecdsaKey := parsedPrivateKey.(*ecdsa.PrivateKey) | ||||
| 			curve := elliptic.P256() | ||||
| 			if p.Type == KeyType_ECDSA_P384 { | ||||
| 				curve = elliptic.P384() | ||||
| 			} else if p.Type == KeyType_ECDSA_P521 { | ||||
| 				curve = elliptic.P521() | ||||
| 			} | ||||
|  | ||||
| 			if ecdsaKey.Curve != curve { | ||||
| 				return fmt.Errorf("invalid curve: expected %s, got %s", curve.Params().Name, ecdsaKey.Curve.Params().Name) | ||||
| 			} | ||||
|  | ||||
| 			entry.EC_D = ecdsaKey.D | ||||
| 			entry.EC_X = ecdsaKey.X | ||||
| 			entry.EC_Y = ecdsaKey.Y | ||||
| 			derBytes, err := x509.MarshalPKIXPublicKey(ecdsaKey.Public()) | ||||
| 			if err != nil { | ||||
| 				return errwrap.Wrapf("error marshaling public key: {{err}}", err) | ||||
| 			} | ||||
| 			pemBlock := &pem.Block{ | ||||
| 				Type:  "PUBLIC KEY", | ||||
| 				Bytes: derBytes, | ||||
| 			} | ||||
| 			pemBytes := pem.EncodeToMemory(pemBlock) | ||||
| 			if pemBytes == nil || len(pemBytes) == 0 { | ||||
| 				return fmt.Errorf("error PEM-encoding public key") | ||||
| 			} | ||||
| 			entry.FormattedPublicKey = string(pemBytes) | ||||
| 		case ed25519.PrivateKey: | ||||
| 			if p.Type != KeyType_ED25519 { | ||||
| 				return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey) | ||||
| 			} | ||||
| 			privateKey := parsedPrivateKey.(ed25519.PrivateKey) | ||||
|  | ||||
| 			entry.Key = privateKey | ||||
| 			publicKey := privateKey.Public().(ed25519.PublicKey) | ||||
| 			entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(publicKey) | ||||
| 		case *rsa.PrivateKey: | ||||
| 			if p.Type != KeyType_RSA2048 && p.Type != KeyType_RSA3072 && p.Type != KeyType_RSA4096 { | ||||
| 				return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey) | ||||
| 			} | ||||
|  | ||||
| 			keyBytes := 256 | ||||
| 			if p.Type == KeyType_RSA3072 { | ||||
| 				keyBytes = 384 | ||||
| 			} else if p.Type == KeyType_RSA4096 { | ||||
| 				keyBytes = 512 | ||||
| 			} | ||||
| 			rsaKey := parsedPrivateKey.(*rsa.PrivateKey) | ||||
| 			if rsaKey.Size() != keyBytes { | ||||
| 				return fmt.Errorf("invalid key size: expected %d bytes, got %d bytes", keyBytes, rsaKey.Size()) | ||||
| 			} | ||||
|  | ||||
| 			entry.RSAKey = rsaKey | ||||
| 		default: | ||||
| 			return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey) | ||||
| 		err = entry.parseFromKey(p.Type, parsedKey) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -2021,8 +2007,13 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		key := keyEntry.RSAKey | ||||
| 		ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, &key.PublicKey, plaintext, nil) | ||||
| 		var publicKey *rsa.PublicKey | ||||
| 		if keyEntry.RSAKey != nil { | ||||
| 			publicKey = &keyEntry.RSAKey.PublicKey | ||||
| 		} else { | ||||
| 			publicKey = keyEntry.RSAPublicKey | ||||
| 		} | ||||
| 		ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil) | ||||
| 		if err != nil { | ||||
| 			return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA encrypt the plaintext: %v", err)} | ||||
| 		} | ||||
| @@ -2067,3 +2058,163 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value | ||||
|  | ||||
| 	return encoded, nil | ||||
| } | ||||
|  | ||||
| func (p *Policy) KeyVersionCanBeUpdated(keyVersion int, isPrivateKey bool) error { | ||||
| 	keyEntry, err := p.safeGetKeyEntry(keyVersion) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !p.Type.ImportPublicKeySupported() { | ||||
| 		return errors.New("provided type does not support importing key versions") | ||||
| 	} | ||||
|  | ||||
| 	isPrivateKeyMissing := keyEntry.IsPrivateKeyMissing() | ||||
| 	if isPrivateKeyMissing && !isPrivateKey { | ||||
| 		return errors.New("cannot add a public key to a key version that already has a public key set") | ||||
| 	} | ||||
|  | ||||
| 	if !isPrivateKeyMissing { | ||||
| 		return errors.New("private key imported, key version cannot be updated") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *Policy) ImportPrivateKeyForVersion(ctx context.Context, storage logical.Storage, keyVersion int, key []byte) error { | ||||
| 	keyEntry, err := p.safeGetKeyEntry(keyVersion) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Parse key | ||||
| 	parsedPrivateKey, err := x509.ParsePKCS8PrivateKey(key) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error parsing asymmetric key: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	switch parsedPrivateKey.(type) { | ||||
| 	case *ecdsa.PrivateKey: | ||||
| 		ecdsaKey := parsedPrivateKey.(*ecdsa.PrivateKey) | ||||
| 		pemBlock, _ := pem.Decode([]byte(keyEntry.FormattedPublicKey)) | ||||
| 		publicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("failed to parse key entry public key: %v", err) | ||||
| 		} | ||||
| 		if !publicKey.(*ecdsa.PublicKey).Equal(ecdsaKey.PublicKey) { | ||||
| 			return fmt.Errorf("cannot import key, key pair does not match") | ||||
| 		} | ||||
| 	case *rsa.PrivateKey: | ||||
| 		rsaKey := parsedPrivateKey.(*rsa.PrivateKey) | ||||
| 		if !rsaKey.PublicKey.Equal(keyEntry.RSAPublicKey) { | ||||
| 			return fmt.Errorf("cannot import key, key pair does not match") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	err = keyEntry.parseFromKey(p.Type, parsedPrivateKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	p.Keys[strconv.Itoa(keyVersion)] = keyEntry | ||||
|  | ||||
| 	return p.Persist(ctx, storage) | ||||
| } | ||||
|  | ||||
| func (ke *KeyEntry) parseFromKey(PolKeyType KeyType, parsedKey any) error { | ||||
| 	switch parsedKey.(type) { | ||||
| 	case *ecdsa.PrivateKey, *ecdsa.PublicKey: | ||||
| 		if PolKeyType != KeyType_ECDSA_P256 && PolKeyType != KeyType_ECDSA_P384 && PolKeyType != KeyType_ECDSA_P521 { | ||||
| 			return fmt.Errorf("invalid key type: expected %s, got %T", PolKeyType, parsedKey) | ||||
| 		} | ||||
|  | ||||
| 		curve := elliptic.P256() | ||||
| 		if PolKeyType == KeyType_ECDSA_P384 { | ||||
| 			curve = elliptic.P384() | ||||
| 		} else if PolKeyType == KeyType_ECDSA_P521 { | ||||
| 			curve = elliptic.P521() | ||||
| 		} | ||||
|  | ||||
| 		var derBytes []byte | ||||
| 		var err error | ||||
| 		ecdsaKey, ok := parsedKey.(*ecdsa.PrivateKey) | ||||
| 		if ok { | ||||
|  | ||||
| 			if ecdsaKey.Curve != curve { | ||||
| 				return fmt.Errorf("invalid curve: expected %s, got %s", curve.Params().Name, ecdsaKey.Curve.Params().Name) | ||||
| 			} | ||||
|  | ||||
| 			ke.EC_D = ecdsaKey.D | ||||
| 			ke.EC_X = ecdsaKey.X | ||||
| 			ke.EC_Y = ecdsaKey.Y | ||||
|  | ||||
| 			derBytes, err = x509.MarshalPKIXPublicKey(ecdsaKey.Public()) | ||||
| 			if err != nil { | ||||
| 				return errwrap.Wrapf("error marshaling public key: {{err}}", err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			ecdsaKey := parsedKey.(*ecdsa.PublicKey) | ||||
|  | ||||
| 			if ecdsaKey.Curve != curve { | ||||
| 				return fmt.Errorf("invalid curve: expected %s, got %s", curve.Params().Name, ecdsaKey.Curve.Params().Name) | ||||
| 			} | ||||
|  | ||||
| 			ke.EC_X = ecdsaKey.X | ||||
| 			ke.EC_Y = ecdsaKey.Y | ||||
|  | ||||
| 			derBytes, err = x509.MarshalPKIXPublicKey(ecdsaKey) | ||||
| 			if err != nil { | ||||
| 				return errwrap.Wrapf("error marshaling public key: {{err}}", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		pemBlock := &pem.Block{ | ||||
| 			Type:  "PUBLIC KEY", | ||||
| 			Bytes: derBytes, | ||||
| 		} | ||||
| 		pemBytes := pem.EncodeToMemory(pemBlock) | ||||
| 		if pemBytes == nil || len(pemBytes) == 0 { | ||||
| 			return fmt.Errorf("error PEM-encoding public key") | ||||
| 		} | ||||
| 		ke.FormattedPublicKey = string(pemBytes) | ||||
| 	case ed25519.PrivateKey: | ||||
| 		if PolKeyType != KeyType_ED25519 { | ||||
| 			return fmt.Errorf("invalid key type: expected %s, got %T", PolKeyType, parsedKey) | ||||
| 		} | ||||
| 		privateKey := parsedKey.(ed25519.PrivateKey) | ||||
|  | ||||
| 		ke.Key = privateKey | ||||
| 		publicKey := privateKey.Public().(ed25519.PublicKey) | ||||
| 		ke.FormattedPublicKey = base64.StdEncoding.EncodeToString(publicKey) | ||||
| 	case *rsa.PrivateKey, *rsa.PublicKey: | ||||
| 		if PolKeyType != KeyType_RSA2048 && PolKeyType != KeyType_RSA3072 && PolKeyType != KeyType_RSA4096 { | ||||
| 			return fmt.Errorf("invalid key type: expected %s, got %T", PolKeyType, parsedKey) | ||||
| 		} | ||||
|  | ||||
| 		keyBytes := 256 | ||||
| 		if PolKeyType == KeyType_RSA3072 { | ||||
| 			keyBytes = 384 | ||||
| 		} else if PolKeyType == KeyType_RSA4096 { | ||||
| 			keyBytes = 512 | ||||
| 		} | ||||
|  | ||||
| 		rsaKey, ok := parsedKey.(*rsa.PrivateKey) | ||||
| 		if ok { | ||||
| 			if rsaKey.Size() != keyBytes { | ||||
| 				return fmt.Errorf("invalid key size: expected %d bytes, got %d bytes", keyBytes, rsaKey.Size()) | ||||
| 			} | ||||
| 			ke.RSAKey = rsaKey | ||||
| 			ke.RSAPublicKey = nil | ||||
| 		} else { | ||||
| 			rsaKey := parsedKey.(*rsa.PublicKey) | ||||
| 			if rsaKey.Size() != keyBytes { | ||||
| 				return fmt.Errorf("invalid key size: expected %d bytes, got %d bytes", keyBytes, rsaKey.Size()) | ||||
| 			} | ||||
| 			ke.RSAPublicKey = rsaKey | ||||
| 		} | ||||
| 	default: | ||||
| 		return fmt.Errorf("invalid key type: expected %s, got %T", PolKeyType, parsedKey) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -846,7 +846,7 @@ func Test_RSA_PSS(t *testing.T) { | ||||
| 		} | ||||
| 		cryptoHash := CryptoHashMap[hashType] | ||||
| 		minSaltLength := p.minRSAPSSSaltLength() | ||||
| 		maxSaltLength := p.maxRSAPSSSaltLength(rsaKey, cryptoHash) | ||||
| 		maxSaltLength := p.maxRSAPSSSaltLength(rsaKey.N.BitLen(), cryptoHash) | ||||
| 		hash := cryptoHash.New() | ||||
| 		hash.Write(input) | ||||
| 		input = hash.Sum(nil) | ||||
|   | ||||
| @@ -109,6 +109,7 @@ $ curl \ | ||||
|  | ||||
| This endpoint imports existing key material into a new transit-managed encryption key. | ||||
| To import key material into an existing key, see the `import_version/` endpoint. | ||||
| // TODO: Has to be updated. | ||||
|  | ||||
| | Method | Path                         | | ||||
| | :----- | :--------------------------- | | ||||
| @@ -125,7 +126,8 @@ returned by Vault and the encryption of the import key material under the | ||||
| provided AES key. The wrapped AES key should be the first 512 bytes of the | ||||
| ciphertext, and the encrypted key material should be the remaining bytes. | ||||
| See the BYOK section of the [Transit secrets engine documentation](/vault/docs/secrets/transit#bring-your-own-key-byok) | ||||
| for more information on constructing the ciphertext. | ||||
| for more information on constructing the ciphertext. If `public_key` is set, | ||||
| this field is not required. | ||||
|  | ||||
| - `hash_function` `(string: "SHA256")` - The hash function used for the | ||||
| RSA-OAEP step of creating the ciphertext. Supported hash functions are: | ||||
| @@ -151,6 +153,9 @@ the hash function defaults to SHA256. | ||||
|   - `rsa-3072` - RSA with bit size of 3072 (asymmetric) | ||||
|   - `rsa-4096` - RSA with bit size of 4096 (asymmetric) | ||||
|  | ||||
| - `public_key` `(string: "", optional)` - A plaintext PEM public key to be imported. | ||||
| If `ciphertext` is set, this field is ignored. | ||||
|  | ||||
| - `allow_rotation` `(bool: false)` - If set, the imported key can be rotated | ||||
| within Vault by using the `rotate` endpoint. | ||||
|  | ||||
| @@ -198,6 +203,7 @@ $ curl \ | ||||
| ## Import Key Version | ||||
|  | ||||
| This endpoint imports new key material into an existing imported key. | ||||
| // TODO: Has to be updated. | ||||
|  | ||||
| | Method | Path                                 | | ||||
| | :----- | :----------------------------------- | | ||||
| @@ -219,12 +225,23 @@ provided AES key. The wrapped AES key should be the first 512 bytes of the | ||||
| ciphertext, and the encrypted key material should be the remaining bytes. | ||||
| See the BYOK section of the [Transit secrets engine documentation](/vault/docs/secrets/transit#bring-your-own-key-byok) | ||||
| for more information on constructing the ciphertext. | ||||
| // TODO: Update text | ||||
|  | ||||
| - `hash_function` `(string: "SHA256")` - The hash function used for the | ||||
| RSA-OAEP step of creating the ciphertext. Supported hash functions are: | ||||
| `SHA1`, `SHA224`, `SHA256`, `SHA384`, and `SHA512`. If not specified, | ||||
| the hash function defaults to SHA256. | ||||
|  | ||||
| - `public_key` `(string: "", optional)` - A plaintext PEM public key to be imported. | ||||
|   If `ciphertext` is set, this field is ignored. | ||||
|  | ||||
| - `bump_version` - By default, each operator will create a new key version. | ||||
| If set to "false", will try to update the latest version of the key, | ||||
| unless changed in parameter `version`. | ||||
|  | ||||
| - `version` - Key version to be updated, if left empty "Latest" version will be updated. | ||||
| If `bump_version` is set to "true", this field is ignored. | ||||
|  | ||||
| ### Sample Payload | ||||
|  | ||||
| ```json | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Gabriel Santos
					Gabriel Santos