mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Update marcellanz/transit_pkcs1v15 RSA encryption support (#25486)
* [transit-pkcs1v15] transit support for the pkcs1v15 padding scheme – without UI tests (yet). * [transit-pkcs1v15] renamed padding_scheme parameter in transit documentation. * [transit-pkcs1v15] add changelog file. * [transit-pkcs1v15] remove the algorithm path as padding_scheme is chosen by parameter. * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update ui/app/templates/components/transit-key-action/datakey.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Update website/content/api-docs/secret/transit.mdx Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> * Add warnings to PKCS1v1.5 usage * Update transit * Update transit, including separating encrypt/decrypt paddings for rewrap * Clean up factory use in the presence of padding * address review feedback * remove defaults * lint * more lint * Some fixes for UI issues - Fix padding scheme dropdown console error by adding values to the transit-key-actions.hbs - Populate both padding scheme drop down menus within rewrap, not just the one padding_scheme - Do not submit a padding_scheme value through POST for non-rsa keys * Fix Transit rewrap API to use decrypt_padding_scheme, encrypt_padding_scheme - Map the appropriate API fields for the RSA padding scheme to the batch items within the rewrap API - Add the ability to create RSA keys within the encrypt API endpoint - Add test case for rewrap api that leverages the padding_scheme fields * Fix code linting issues * simply padding scheme enum * Apply suggestions from code review Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * Fix padding_scheme processing on data key api - The data key api was using the incorrect parameter name for the padding scheme - Enforce that padding_scheme is only used on RSA keys, we are punting on supporting it for managed keys at the moment. * Add tests for parsePaddingSchemeArg * Add missing copywrite headers * Some small UI fixes * Add missing param to datakey in api-docs * Do not send padding_scheme for non-RSA key types within UI * add UI tests for transit key actions form --------- Co-authored-by: Marcel Lanz <marcellanz@n-1.ch> Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Co-authored-by: Sarah Chavis <62406755+schavis@users.noreply.github.com> Co-authored-by: Steve Clark <steven.clark@hashicorp.com> Co-authored-by: claire bontempo <cbontempo@hashicorp.com>
This commit is contained in:
29
builtin/logical/transit/api_utils.go
Normal file
29
builtin/logical/transit/api_utils.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package transit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
||||
)
|
||||
|
||||
// parsePaddingSchemeArg validate that the provided padding scheme argument received on the api can be used.
|
||||
func parsePaddingSchemeArg(keyType keysutil.KeyType, rawPs any) (keysutil.PaddingScheme, error) {
|
||||
ps, ok := rawPs.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("argument was not a string: %T", rawPs)
|
||||
}
|
||||
|
||||
paddingScheme, err := keysutil.ParsePaddingScheme(ps)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !keyType.PaddingSchemesSupported() {
|
||||
return "", fmt.Errorf("unsupported key type %s for padding scheme", keyType.String())
|
||||
}
|
||||
|
||||
return paddingScheme, nil
|
||||
}
|
||||
52
builtin/logical/transit/api_utils_test.go
Normal file
52
builtin/logical/transit/api_utils_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package transit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
||||
)
|
||||
|
||||
// Test_parsePaddingSchemeArg validate the various use cases we have around parsing
|
||||
// the various padding_scheme arg possible values.
|
||||
func Test_parsePaddingSchemeArg(t *testing.T) {
|
||||
type args struct {
|
||||
keyType keysutil.KeyType
|
||||
rawPs any
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want keysutil.PaddingScheme
|
||||
wantErr bool
|
||||
}{
|
||||
// Error cases
|
||||
{name: "nil-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: nil}, wantErr: true},
|
||||
{name: "nonstring-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: 5}, wantErr: true},
|
||||
{name: "invalid-ps", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: "unknown"}, wantErr: true},
|
||||
{name: "bad-keytype-oaep", args: args{keyType: keysutil.KeyType_AES128_CMAC, rawPs: "oaep"}, wantErr: true},
|
||||
{name: "bad-keytype-pkcs1", args: args{keyType: keysutil.KeyType_ECDSA_P256, rawPs: "pkcs1v15"}, wantErr: true},
|
||||
{name: "oaep-capped", args: args{keyType: keysutil.KeyType_RSA4096, rawPs: "OAEP"}, wantErr: true},
|
||||
{name: "pkcs1-whitespace", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: " pkcs1v15 "}, wantErr: true},
|
||||
|
||||
// Valid cases
|
||||
{name: "oaep-2048", args: args{keyType: keysutil.KeyType_RSA2048, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP},
|
||||
{name: "oaep-3072", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP},
|
||||
{name: "oaep-4096", args: args{keyType: keysutil.KeyType_RSA4096, rawPs: "oaep"}, want: keysutil.PaddingScheme_OAEP},
|
||||
{name: "pkcs1", args: args{keyType: keysutil.KeyType_RSA3072, rawPs: "pkcs1v15"}, want: keysutil.PaddingScheme_PKCS1v15},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parsePaddingSchemeArg(tt.args.keyType, tt.args.rawPs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parsePaddingSchemeArg() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("parsePaddingSchemeArg() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -148,83 +148,96 @@ func testTransit_RSA(t *testing.T, keyType string) {
|
||||
|
||||
plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox"
|
||||
|
||||
encryptReq := &logical.Request{
|
||||
Path: "encrypt/rsa",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
Data: map[string]interface{}{
|
||||
"plaintext": plaintext,
|
||||
},
|
||||
}
|
||||
for _, padding := range []keysutil.PaddingScheme{keysutil.PaddingScheme_OAEP, keysutil.PaddingScheme_PKCS1v15, ""} {
|
||||
encryptReq := &logical.Request{
|
||||
Path: "encrypt/rsa",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
Data: map[string]interface{}{
|
||||
"plaintext": plaintext,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), encryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if padding != "" {
|
||||
encryptReq.Data["padding_scheme"] = padding
|
||||
}
|
||||
|
||||
ciphertext1 := resp.Data["ciphertext"].(string)
|
||||
resp, err = b.HandleRequest(context.Background(), encryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
decryptReq := &logical.Request{
|
||||
Path: "decrypt/rsa",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
Data: map[string]interface{}{
|
||||
"ciphertext": ciphertext1,
|
||||
},
|
||||
}
|
||||
ciphertext1 := resp.Data["ciphertext"].(string)
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
decryptReq := &logical.Request{
|
||||
Path: "decrypt/rsa",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
Data: map[string]interface{}{
|
||||
"ciphertext": ciphertext1,
|
||||
},
|
||||
}
|
||||
if padding != "" {
|
||||
decryptReq.Data["padding_scheme"] = padding
|
||||
}
|
||||
|
||||
decryptedPlaintext := resp.Data["plaintext"]
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
if plaintext != decryptedPlaintext {
|
||||
t.Fatalf("bad: plaintext; expected: %q\nactual: %q", plaintext, decryptedPlaintext)
|
||||
}
|
||||
decryptedPlaintext := resp.Data["plaintext"]
|
||||
|
||||
// Rotate the key
|
||||
rotateReq := &logical.Request{
|
||||
Path: "keys/rsa/rotate",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), rotateReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if plaintext != decryptedPlaintext {
|
||||
t.Fatalf("bad: plaintext; expected: %q\nactual: %q", plaintext, decryptedPlaintext)
|
||||
}
|
||||
|
||||
// Encrypt again
|
||||
resp, err = b.HandleRequest(context.Background(), encryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
ciphertext2 := resp.Data["ciphertext"].(string)
|
||||
// Rotate the key
|
||||
rotateReq := &logical.Request{
|
||||
Path: "keys/rsa/rotate",
|
||||
Operation: logical.UpdateOperation,
|
||||
Storage: storage,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), rotateReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
if ciphertext1 == ciphertext2 {
|
||||
t.Fatalf("expected different ciphertexts")
|
||||
}
|
||||
// Encrypt again
|
||||
resp, err = b.HandleRequest(context.Background(), encryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
ciphertext2 := resp.Data["ciphertext"].(string)
|
||||
|
||||
// See if the older ciphertext can still be decrypted
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["plaintext"].(string) != plaintext {
|
||||
t.Fatal("failed to decrypt old ciphertext after rotating the key")
|
||||
}
|
||||
if ciphertext1 == ciphertext2 {
|
||||
t.Fatalf("expected different ciphertexts")
|
||||
}
|
||||
|
||||
// Decrypt the new ciphertext
|
||||
decryptReq.Data = map[string]interface{}{
|
||||
"ciphertext": ciphertext2,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["plaintext"].(string) != plaintext {
|
||||
t.Fatal("failed to decrypt ciphertext after rotating the key")
|
||||
// See if the older ciphertext can still be decrypted
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["plaintext"].(string) != plaintext {
|
||||
t.Fatal("failed to decrypt old ciphertext after rotating the key")
|
||||
}
|
||||
|
||||
// Decrypt the new ciphertext
|
||||
decryptReq.Data = map[string]interface{}{
|
||||
"ciphertext": ciphertext2,
|
||||
}
|
||||
if padding != "" {
|
||||
decryptReq.Data["padding_scheme"] = padding
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["plaintext"].(string) != plaintext {
|
||||
t.Fatal("failed to decrypt ciphertext after rotating the key")
|
||||
}
|
||||
}
|
||||
|
||||
signReq := &logical.Request{
|
||||
|
||||
@@ -39,6 +39,12 @@ func (b *backend) pathDatakey() *framework.Path {
|
||||
ciphertext; "wrapped" will return the ciphertext only.`,
|
||||
},
|
||||
|
||||
"padding_scheme": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types.
|
||||
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
|
||||
},
|
||||
|
||||
"context": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Context for key derivation. Required for derived keys.",
|
||||
@@ -142,23 +148,31 @@ func (b *backend) pathDatakeyWrite(ctx context.Context, req *logical.Request, d
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var managedKeyFactory ManagedKeyFactory
|
||||
factories := make([]any, 0)
|
||||
if ps, ok := d.GetOk("padding_scheme"); ok {
|
||||
paddingScheme, err := parsePaddingSchemeArg(p.Type, ps)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("padding_scheme argument invalid: %s", err.Error())), logical.ErrInvalidRequest
|
||||
}
|
||||
factories = append(factories, paddingScheme)
|
||||
|
||||
}
|
||||
if p.Type == keysutil.KeyType_MANAGED_KEY {
|
||||
managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView)
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported system view")
|
||||
}
|
||||
|
||||
managedKeyFactory = ManagedKeyFactory{
|
||||
factories = append(factories, ManagedKeyFactory{
|
||||
managedKeyParams: keysutil.ManagedKeyParameters{
|
||||
ManagedKeySystemView: managedKeySystemView,
|
||||
BackendUUID: b.backendUUID,
|
||||
Context: ctx,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ciphertext, err := p.EncryptWithFactory(ver, context, nonce, base64.StdEncoding.EncodeToString(newKey), nil, managedKeyFactory)
|
||||
ciphertext, err := p.EncryptWithFactory(ver, context, nonce, base64.StdEncoding.EncodeToString(newKey), factories...)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.UserError:
|
||||
|
||||
125
builtin/logical/transit/path_datakey_test.go
Normal file
125
builtin/logical/transit/path_datakey_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package transit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestDataKeyWithPaddingScheme validates that we properly leverage padding scheme
|
||||
// args for the returned keys
|
||||
func TestDataKeyWithPaddingScheme(t *testing.T) {
|
||||
b, s := createBackendWithStorage(t)
|
||||
keyName := "test"
|
||||
createKeyReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/" + keyName,
|
||||
Storage: s,
|
||||
Data: map[string]interface{}{
|
||||
"type": "rsa-2048",
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), createKeyReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("failed key creation: err: %v resp: %#v", err, resp)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Name string
|
||||
PaddingScheme string
|
||||
DecryptPaddingScheme string
|
||||
ShouldFailToDecrypt bool
|
||||
}{
|
||||
{"no-padding-scheme", "", "", false},
|
||||
{"oaep", "oaep", "oaep", false},
|
||||
{"pkcs1v15", "pkcs1v15", "pkcs1v15", false},
|
||||
{"mixed-should-fail", "pkcs1v15", "oaep", true},
|
||||
{"mixed-based-on-default-should-fail", "", "pkcs1v15", true},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
dataKeyReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "datakey/wrapped/" + keyName,
|
||||
Storage: s,
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
if len(tc.PaddingScheme) > 0 {
|
||||
dataKeyReq.Data["padding_scheme"] = tc.PaddingScheme
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), dataKeyReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("failed data key api: err: %v resp: %#v", err, resp)
|
||||
}
|
||||
require.NotNil(t, resp, "Got nil nil response")
|
||||
var d struct {
|
||||
Ciphertext string `mapstructure:"ciphertext"`
|
||||
}
|
||||
err = mapstructure.Decode(resp.Data, &d)
|
||||
require.NoError(t, err, "failed decoding datakey api response")
|
||||
require.NotEmpty(t, d.Ciphertext, "ciphertext should not be empty")
|
||||
|
||||
// Attempt to decrypt with data key with the same padding scheme
|
||||
decryptReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "decrypt/" + keyName,
|
||||
Storage: s,
|
||||
Data: map[string]interface{}{
|
||||
"ciphertext": d.Ciphertext,
|
||||
},
|
||||
}
|
||||
if len(tc.DecryptPaddingScheme) > 0 {
|
||||
decryptReq.Data["padding_scheme"] = tc.DecryptPaddingScheme
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), decryptReq)
|
||||
if tc.ShouldFailToDecrypt {
|
||||
require.Error(t, err, "Should have failed decryption as padding schemes are mixed")
|
||||
} else {
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("failed to decrypt data key: err: %v resp: %#v", err, resp)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDataKeyWithPaddingSchemeInvalidKeyType validates we fail when we specify a
|
||||
// padding_scheme value on an invalid key type (non-RSA)
|
||||
func TestDataKeyWithPaddingSchemeInvalidKeyType(t *testing.T) {
|
||||
b, s := createBackendWithStorage(t)
|
||||
keyName := "test"
|
||||
createKeyReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/" + keyName,
|
||||
Storage: s,
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), createKeyReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("failed key creation: err: %v resp: %#v", err, resp)
|
||||
}
|
||||
|
||||
dataKeyReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "datakey/wrapped/" + keyName,
|
||||
Storage: s,
|
||||
Data: map[string]interface{}{
|
||||
"padding_scheme": "oaep",
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), dataKeyReq)
|
||||
require.ErrorContains(t, err, "invalid request")
|
||||
require.NotNil(t, resp, "response should not be nil")
|
||||
require.Contains(t, resp.Error().Error(), "padding_scheme argument invalid: unsupported key")
|
||||
}
|
||||
@@ -50,6 +50,12 @@ func (b *backend) pathDecrypt() *framework.Path {
|
||||
The ciphertext to decrypt, provided as returned by encrypt.`,
|
||||
},
|
||||
|
||||
"padding_scheme": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types.
|
||||
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
|
||||
},
|
||||
|
||||
"context": {
|
||||
Type: framework.TypeString,
|
||||
Description: `
|
||||
@@ -130,6 +136,9 @@ func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, d
|
||||
Nonce: d.Get("nonce").(string),
|
||||
AssociatedData: d.Get("associated_data").(string),
|
||||
}
|
||||
if ps, ok := d.GetOk("padding_scheme"); ok {
|
||||
batchInputItems[0].PaddingScheme = ps.(string)
|
||||
}
|
||||
}
|
||||
|
||||
batchResponseItems := make([]DecryptBatchResponseItem, len(batchInputItems))
|
||||
@@ -192,33 +201,40 @@ func (b *backend) pathDecryptWrite(ctx context.Context, req *logical.Request, d
|
||||
continue
|
||||
}
|
||||
|
||||
var factory interface{}
|
||||
var factories []any
|
||||
if item.PaddingScheme != "" {
|
||||
paddingScheme, err := parsePaddingSchemeArg(p.Type, item.PaddingScheme)
|
||||
if err != nil {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].padding_scheme' invalid: %s", i, err.Error())
|
||||
continue
|
||||
}
|
||||
factories = append(factories, paddingScheme)
|
||||
}
|
||||
if item.AssociatedData != "" {
|
||||
if !p.Type.AssociatedDataSupported() {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].associated_data' provided for non-AEAD cipher suite %v", i, p.Type.String())
|
||||
continue
|
||||
}
|
||||
|
||||
factory = AssocDataFactory{item.AssociatedData}
|
||||
factories = append(factories, AssocDataFactory{item.AssociatedData})
|
||||
}
|
||||
|
||||
var managedKeyFactory ManagedKeyFactory
|
||||
if p.Type == keysutil.KeyType_MANAGED_KEY {
|
||||
managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView)
|
||||
if !ok {
|
||||
batchResponseItems[i].Error = errors.New("unsupported system view").Error()
|
||||
}
|
||||
|
||||
managedKeyFactory = ManagedKeyFactory{
|
||||
factories = append(factories, ManagedKeyFactory{
|
||||
managedKeyParams: keysutil.ManagedKeyParameters{
|
||||
ManagedKeySystemView: managedKeySystemView,
|
||||
BackendUUID: b.backendUUID,
|
||||
Context: ctx,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factory, managedKeyFactory)
|
||||
plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factories...)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.InternalError:
|
||||
|
||||
@@ -34,6 +34,9 @@ type BatchRequestItem struct {
|
||||
// Ciphertext for decryption
|
||||
Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"`
|
||||
|
||||
// PaddingScheme for encryption/decryption
|
||||
PaddingScheme string `json:"padding_scheme" structs:"padding_scheme" mapstructure:"padding_scheme"`
|
||||
|
||||
// Nonce to be used when v1 convergent encryption is used
|
||||
Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"`
|
||||
|
||||
@@ -105,6 +108,12 @@ func (b *backend) pathEncrypt() *framework.Path {
|
||||
Description: "Base64 encoded plaintext value to be encrypted",
|
||||
},
|
||||
|
||||
"padding_scheme": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The padding scheme to use for decrypt. Currently only applies to RSA key types.
|
||||
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
|
||||
},
|
||||
|
||||
"context": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Base64 encoded context for key derivation. Required if key derivation is enabled",
|
||||
@@ -259,6 +268,13 @@ func decodeBatchRequestItems(src interface{}, requirePlaintext bool, requireCiph
|
||||
} else if requirePlaintext {
|
||||
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].plaintext' missing plaintext to encrypt", i))
|
||||
}
|
||||
if v, has := item["padding_scheme"]; has {
|
||||
if casted, ok := v.(string); ok {
|
||||
(*dst)[i].PaddingScheme = casted
|
||||
} else {
|
||||
errs.Errors = append(errs.Errors, fmt.Sprintf("'[%d].padding_scheme' expected type 'string', got unconvertible type '%T'", i, item["padding_scheme"]))
|
||||
}
|
||||
}
|
||||
|
||||
if v, has := item["nonce"]; has {
|
||||
if !reflect.ValueOf(v).IsValid() {
|
||||
@@ -358,6 +374,13 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d
|
||||
KeyVersion: d.Get("key_version").(int),
|
||||
AssociatedData: d.Get("associated_data").(string),
|
||||
}
|
||||
if psRaw, ok := d.GetOk("padding_scheme"); ok {
|
||||
if ps, ok := psRaw.(string); ok {
|
||||
batchInputItems[0].PaddingScheme = ps
|
||||
} else {
|
||||
return logical.ErrorResponse("padding_scheme was not a string"), logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batchResponseItems := make([]EncryptBatchResponseItem, len(batchInputItems))
|
||||
@@ -435,6 +458,12 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d
|
||||
polReq.KeyType = keysutil.KeyType_AES256_GCM96
|
||||
case "chacha20-poly1305":
|
||||
polReq.KeyType = keysutil.KeyType_ChaCha20_Poly1305
|
||||
case "rsa-2048":
|
||||
polReq.KeyType = keysutil.KeyType_RSA2048
|
||||
case "rsa-3072":
|
||||
polReq.KeyType = keysutil.KeyType_RSA3072
|
||||
case "rsa-4096":
|
||||
polReq.KeyType = keysutil.KeyType_RSA4096
|
||||
case "ecdsa-p256", "ecdsa-p384", "ecdsa-p521":
|
||||
return logical.ErrorResponse(fmt.Sprintf("key type %v not supported for this operation", keyType)), logical.ErrInvalidRequest
|
||||
case "managed_key":
|
||||
@@ -482,33 +511,40 @@ func (b *backend) pathEncryptWrite(ctx context.Context, req *logical.Request, d
|
||||
warnAboutNonceUsage = true
|
||||
}
|
||||
|
||||
var factory interface{}
|
||||
var factories []any
|
||||
if item.PaddingScheme != "" {
|
||||
paddingScheme, err := parsePaddingSchemeArg(p.Type, item.PaddingScheme)
|
||||
if err != nil {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].padding_scheme' invalid: %s", i, err.Error())
|
||||
continue
|
||||
}
|
||||
factories = append(factories, paddingScheme)
|
||||
}
|
||||
if item.AssociatedData != "" {
|
||||
if !p.Type.AssociatedDataSupported() {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].associated_data' provided for non-AEAD cipher suite %v", i, p.Type.String())
|
||||
continue
|
||||
}
|
||||
|
||||
factory = AssocDataFactory{item.AssociatedData}
|
||||
factories = append(factories, AssocDataFactory{item.AssociatedData})
|
||||
}
|
||||
|
||||
var managedKeyFactory ManagedKeyFactory
|
||||
if p.Type == keysutil.KeyType_MANAGED_KEY {
|
||||
managedKeySystemView, ok := b.System().(logical.ManagedKeySystemView)
|
||||
if !ok {
|
||||
batchResponseItems[i].Error = errors.New("unsupported system view").Error()
|
||||
}
|
||||
|
||||
managedKeyFactory = ManagedKeyFactory{
|
||||
factories = append(factories, ManagedKeyFactory{
|
||||
managedKeyParams: keysutil.ManagedKeyParameters{
|
||||
ManagedKeySystemView: managedKeySystemView,
|
||||
BackendUUID: b.backendUUID,
|
||||
Context: ctx,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, item.Plaintext, factory, managedKeyFactory)
|
||||
ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, item.Plaintext, factories...)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.InternalError:
|
||||
|
||||
@@ -19,6 +19,39 @@ import (
|
||||
|
||||
var ErrNonceNotAllowed = errors.New("provided nonce not allowed for this key")
|
||||
|
||||
type RewrapBatchRequestItem struct {
|
||||
// Context for key derivation. This is required for derived keys.
|
||||
Context string `json:"context" structs:"context" mapstructure:"context"`
|
||||
|
||||
// DecodedContext is the base64 decoded version of Context
|
||||
DecodedContext []byte
|
||||
|
||||
// Ciphertext for decryption
|
||||
Ciphertext string `json:"ciphertext" structs:"ciphertext" mapstructure:"ciphertext"`
|
||||
|
||||
// Nonce to be used when v1 convergent encryption is used
|
||||
Nonce string `json:"nonce" structs:"nonce" mapstructure:"nonce"`
|
||||
|
||||
// The key version to be used for encryption
|
||||
KeyVersion int `json:"key_version" structs:"key_version" mapstructure:"key_version"`
|
||||
|
||||
// DecodedNonce is the base64 decoded version of Nonce
|
||||
DecodedNonce []byte
|
||||
|
||||
// Associated Data for AEAD ciphers
|
||||
AssociatedData string `json:"associated_data" struct:"associated_data" mapstructure:"associated_data"`
|
||||
|
||||
// Reference is an arbitrary caller supplied string value that will be placed on the
|
||||
// batch response to ease correlation between inputs and outputs
|
||||
Reference string `json:"reference" structs:"reference" mapstructure:"reference"`
|
||||
|
||||
// EncryptPaddingScheme specifies the RSA padding scheme for encryption
|
||||
EncryptPaddingScheme string `json:"encrypt_padding_scheme" structs:"encrypt_padding_scheme" mapstructure:"encrypt_padding_scheme"`
|
||||
|
||||
// DecryptPaddingScheme specifies the RSA padding scheme for decryption
|
||||
DecryptPaddingScheme string `json:"decrypt_padding_scheme" structs:"decrypt_padding_scheme" mapstructure:"decrypt_padding_scheme"`
|
||||
}
|
||||
|
||||
func (b *backend) pathRewrap() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "rewrap/" + framework.GenericNameRegex("name"),
|
||||
@@ -39,6 +72,18 @@ func (b *backend) pathRewrap() *framework.Path {
|
||||
Description: "Ciphertext value to rewrap",
|
||||
},
|
||||
|
||||
"encrypt_padding_scheme": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The padding scheme to use for rewrap's encrypt step. Currently only applies to RSA key types.
|
||||
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
|
||||
},
|
||||
|
||||
"decrypt_padding_scheme": {
|
||||
Type: framework.TypeString,
|
||||
Description: `The padding scheme to use for rewrap's decrypt step. Currently only applies to RSA key types.
|
||||
Options are 'oaep' or 'pkcs1v15'. Defaults to 'oaep'`,
|
||||
},
|
||||
|
||||
"context": {
|
||||
Type: framework.TypeString,
|
||||
Description: "Base64 encoded context for key derivation. Required for derived keys.",
|
||||
@@ -76,7 +121,7 @@ Any batch output will preserve the order of the batch input.`,
|
||||
|
||||
func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
batchInputRaw := d.Raw["batch_input"]
|
||||
var batchInputItems []BatchRequestItem
|
||||
var batchInputItems []RewrapBatchRequestItem
|
||||
var err error
|
||||
if batchInputRaw != nil {
|
||||
err = mapstructure.Decode(batchInputRaw, &batchInputItems)
|
||||
@@ -93,13 +138,19 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *
|
||||
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
batchInputItems = make([]BatchRequestItem, 1)
|
||||
batchInputItems[0] = BatchRequestItem{
|
||||
batchInputItems = make([]RewrapBatchRequestItem, 1)
|
||||
batchInputItems[0] = RewrapBatchRequestItem{
|
||||
Ciphertext: ciphertext,
|
||||
Context: d.Get("context").(string),
|
||||
Nonce: d.Get("nonce").(string),
|
||||
KeyVersion: d.Get("key_version").(int),
|
||||
}
|
||||
if ps, ok := d.GetOk("decrypt_padding_scheme"); ok {
|
||||
batchInputItems[0].DecryptPaddingScheme = ps.(string)
|
||||
}
|
||||
if ps, ok := d.GetOk("encrypt_padding_scheme"); ok {
|
||||
batchInputItems[0].EncryptPaddingScheme = ps.(string)
|
||||
}
|
||||
}
|
||||
|
||||
batchResponseItems := make([]EncryptBatchResponseItem, len(batchInputItems))
|
||||
@@ -156,12 +207,21 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *
|
||||
continue
|
||||
}
|
||||
|
||||
var factories []any
|
||||
if item.DecryptPaddingScheme != "" {
|
||||
paddingScheme, err := parsePaddingSchemeArg(p.Type, item.DecryptPaddingScheme)
|
||||
if err != nil {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].decrypt_padding_scheme' invalid: %s", i, err.Error())
|
||||
continue
|
||||
}
|
||||
factories = append(factories, paddingScheme)
|
||||
}
|
||||
if item.Nonce != "" && !nonceAllowed(p) {
|
||||
batchResponseItems[i].Error = ErrNonceNotAllowed.Error()
|
||||
continue
|
||||
}
|
||||
|
||||
plaintext, err := p.Decrypt(item.DecodedContext, item.DecodedNonce, item.Ciphertext)
|
||||
plaintext, err := p.DecryptWithFactory(item.DecodedContext, item.DecodedNonce, item.Ciphertext, factories...)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.UserError:
|
||||
@@ -172,11 +232,21 @@ func (b *backend) pathRewrapWrite(ctx context.Context, req *logical.Request, d *
|
||||
}
|
||||
}
|
||||
|
||||
factories = make([]any, 0)
|
||||
if item.EncryptPaddingScheme != "" {
|
||||
paddingScheme, err := parsePaddingSchemeArg(p.Type, item.EncryptPaddingScheme)
|
||||
if err != nil {
|
||||
batchResponseItems[i].Error = fmt.Sprintf("'[%d].encrypt_padding_scheme' invalid: %s", i, err.Error())
|
||||
continue
|
||||
}
|
||||
factories = append(factories, paddingScheme)
|
||||
factories = append(factories, keysutil.PaddingScheme(item.EncryptPaddingScheme))
|
||||
}
|
||||
if !warnAboutNonceUsage && shouldWarnAboutNonceUsage(p, item.DecodedNonce) {
|
||||
warnAboutNonceUsage = true
|
||||
}
|
||||
|
||||
ciphertext, err := p.Encrypt(item.KeyVersion, item.DecodedContext, item.DecodedNonce, plaintext)
|
||||
ciphertext, err := p.EncryptWithFactory(item.KeyVersion, item.DecodedContext, item.DecodedNonce, plaintext, factories...)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.UserError:
|
||||
|
||||
@@ -326,3 +326,116 @@ func TestTransit_BatchRewrapCase3(t *testing.T) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransit_BatchRewrapCase4 batch rewrap leveraging RSA padding schemes
|
||||
func TestTransit_BatchRewrapCase4(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
|
||||
b, s := createBackendWithStorage(t)
|
||||
|
||||
batchEncryptionInput := []interface{}{
|
||||
map[string]interface{}{"plaintext": "dmlzaGFsCg==", "reference": "ek", "padding_scheme": "pkcs1v15"},
|
||||
map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "reference": "do", "padding_scheme": "pkcs1v15"},
|
||||
}
|
||||
batchEncryptionData := map[string]interface{}{
|
||||
"type": "rsa-2048",
|
||||
"batch_input": batchEncryptionInput,
|
||||
}
|
||||
batchReq := &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "encrypt/upserted_key",
|
||||
Storage: s,
|
||||
Data: batchEncryptionData,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), batchReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
batchEncryptionResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
|
||||
|
||||
batchRewrapInput := make([]interface{}, len(batchEncryptionResponseItems))
|
||||
for i, item := range batchEncryptionResponseItems {
|
||||
batchRewrapInput[i] = map[string]interface{}{
|
||||
"ciphertext": item.Ciphertext,
|
||||
"reference": item.Reference,
|
||||
"decrypt_padding_scheme": "pkcs1v15",
|
||||
"encrypt_padding_scheme": "oaep",
|
||||
}
|
||||
}
|
||||
|
||||
batchRewrapData := map[string]interface{}{
|
||||
"batch_input": batchRewrapInput,
|
||||
}
|
||||
|
||||
rotateReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/upserted_key/rotate",
|
||||
Storage: s,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), rotateReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
rewrapReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "rewrap/upserted_key",
|
||||
Storage: s,
|
||||
Data: batchRewrapData,
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), rewrapReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
batchRewrapResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
|
||||
|
||||
if len(batchRewrapResponseItems) != len(batchEncryptionResponseItems) {
|
||||
t.Fatalf("bad: length of input and output or rewrap are not matching; expected: %d, actual: %d", len(batchEncryptionResponseItems), len(batchRewrapResponseItems))
|
||||
}
|
||||
|
||||
decReq := &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "decrypt/upserted_key",
|
||||
Storage: s,
|
||||
}
|
||||
|
||||
for i, eItem := range batchEncryptionResponseItems {
|
||||
rItem := batchRewrapResponseItems[i]
|
||||
|
||||
inputRef := batchEncryptionInput[i].(map[string]interface{})["reference"]
|
||||
if eItem.Reference != inputRef {
|
||||
t.Fatalf("bad: reference mismatch. Expected %s, Actual: %s", inputRef, eItem.Reference)
|
||||
}
|
||||
|
||||
if eItem.Ciphertext == rItem.Ciphertext {
|
||||
t.Fatalf("bad: rewrap input and output are the same")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(rItem.Ciphertext, "vault:v2") {
|
||||
t.Fatalf("bad: invalid version of ciphertext in rewrap response; expected: 'vault:v2', actual: %s", rItem.Ciphertext)
|
||||
}
|
||||
|
||||
if rItem.KeyVersion != 2 {
|
||||
t.Fatalf("unexpected key version; got: %d, expected: %d", rItem.KeyVersion, 2)
|
||||
}
|
||||
|
||||
decReq.Data = map[string]interface{}{
|
||||
"ciphertext": rItem.Ciphertext,
|
||||
}
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), decReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("err:%v resp:%#v", err, resp)
|
||||
}
|
||||
|
||||
plaintext1 := "dGhlIHF1aWNrIGJyb3duIGZveA=="
|
||||
plaintext2 := "dmlzaGFsCg=="
|
||||
if resp.Data["plaintext"] != plaintext1 && resp.Data["plaintext"] != plaintext2 {
|
||||
t.Fatalf("bad: plaintext. Expected: %q or %q, Actual: %q", plaintext1, plaintext2, resp.Data["plaintext"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
changelog/25486.txt
Normal file
3
changelog/25486.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
secrets/transit: Add support for RSA padding scheme pkcs1v15 for encryption
|
||||
```
|
||||
@@ -82,6 +82,30 @@ const (
|
||||
DefaultVersionTemplate = "vault:v{{version}}:"
|
||||
)
|
||||
|
||||
type PaddingScheme string
|
||||
|
||||
const (
|
||||
PaddingScheme_OAEP = PaddingScheme("oaep")
|
||||
PaddingScheme_PKCS1v15 = PaddingScheme("pkcs1v15")
|
||||
)
|
||||
|
||||
func (p PaddingScheme) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// ParsePaddingScheme expects a lower case string that can be directly compared to
|
||||
// a defined padding scheme or returns an error.
|
||||
func ParsePaddingScheme(s string) (PaddingScheme, error) {
|
||||
switch s {
|
||||
case PaddingScheme_OAEP.String():
|
||||
return PaddingScheme_OAEP, nil
|
||||
case PaddingScheme_PKCS1v15.String():
|
||||
return PaddingScheme_PKCS1v15, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown padding scheme: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
type AEADFactory interface {
|
||||
GetAEAD(iv []byte) (cipher.AEAD, error)
|
||||
}
|
||||
@@ -199,6 +223,15 @@ func (kt KeyType) ImportPublicKeySupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (kt KeyType) PaddingSchemesSupported() bool {
|
||||
switch kt {
|
||||
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (kt KeyType) String() string {
|
||||
switch kt {
|
||||
case KeyType_AES128_GCM96:
|
||||
@@ -939,7 +972,7 @@ func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
||||
return p.DecryptWithFactory(context, nonce, value, nil)
|
||||
}
|
||||
|
||||
func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factories ...interface{}) (string, error) {
|
||||
func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factories ...any) (string, error) {
|
||||
if !p.Type.DecryptionSupported() {
|
||||
return "", errutil.UserError{Err: fmt.Sprintf("message decryption not supported for key type %v", p.Type)}
|
||||
}
|
||||
@@ -1034,15 +1067,24 @@ func (p *Policy) DecryptWithFactory(context, nonce []byte, value string, factori
|
||||
return "", err
|
||||
}
|
||||
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
||||
paddingScheme, err := getPaddingScheme(factories)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
keyEntry, err := p.safeGetKeyEntry(ver)
|
||||
if err != nil {
|
||||
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")}
|
||||
|
||||
switch paddingScheme {
|
||||
case PaddingScheme_PKCS1v15:
|
||||
plain, err = rsa.DecryptPKCS1v15(rand.Reader, key, decoded)
|
||||
case PaddingScheme_OAEP:
|
||||
plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil)
|
||||
default:
|
||||
return "", errutil.InternalError{Err: fmt.Sprintf("unsupported RSA padding scheme %s", paddingScheme)}
|
||||
}
|
||||
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)}
|
||||
}
|
||||
@@ -2033,7 +2075,7 @@ func (p *Policy) SymmetricDecryptRaw(encKey, ciphertext []byte, opts SymmetricOp
|
||||
return plain, nil
|
||||
}
|
||||
|
||||
func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value string, factories ...interface{}) (string, error) {
|
||||
func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value string, factories ...any) (string, error) {
|
||||
if !p.Type.EncryptionSupported() {
|
||||
return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)}
|
||||
}
|
||||
@@ -2128,6 +2170,10 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value
|
||||
return "", err
|
||||
}
|
||||
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
||||
paddingScheme, err := getPaddingScheme(factories)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
keyEntry, err := p.safeGetKeyEntry(ver)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -2138,7 +2184,15 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value
|
||||
} else {
|
||||
publicKey = keyEntry.RSAPublicKey
|
||||
}
|
||||
ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil)
|
||||
switch paddingScheme {
|
||||
case PaddingScheme_PKCS1v15:
|
||||
ciphertext, err = rsa.EncryptPKCS1v15(rand.Reader, publicKey, plaintext)
|
||||
case PaddingScheme_OAEP:
|
||||
ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, plaintext, nil)
|
||||
default:
|
||||
return "", errutil.InternalError{Err: fmt.Sprintf("unsupported RSA padding scheme %s", paddingScheme)}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA encrypt the plaintext: %v", err)}
|
||||
}
|
||||
@@ -2184,6 +2238,19 @@ func (p *Policy) EncryptWithFactory(ver int, context []byte, nonce []byte, value
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
func getPaddingScheme(factories []any) (PaddingScheme, error) {
|
||||
for _, rawFactory := range factories {
|
||||
if rawFactory == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if p, ok := rawFactory.(PaddingScheme); ok && p != "" {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
return PaddingScheme_OAEP, nil
|
||||
}
|
||||
|
||||
func (p *Policy) KeyVersionCanBeUpdated(keyVersion int, isPrivateKey bool) error {
|
||||
keyEntry, err := p.safeGetKeyEntry(keyVersion)
|
||||
if err != nil {
|
||||
@@ -2379,7 +2446,7 @@ func (ke *KeyEntry) parseFromKey(PolKeyType KeyType, parsedKey any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Policy) WrapKey(ver int, targetKey interface{}, targetKeyType KeyType, hash hash.Hash) (string, error) {
|
||||
func (p *Policy) WrapKey(ver int, targetKey any, targetKeyType KeyType, hash hash.Hash) (string, error) {
|
||||
if !p.Type.SigningSupported() {
|
||||
return "", fmt.Errorf("message signing not supported for key type %v", p.Type)
|
||||
}
|
||||
@@ -2403,7 +2470,7 @@ func (p *Policy) WrapKey(ver int, targetKey interface{}, targetKeyType KeyType,
|
||||
return keyEntry.WrapKey(targetKey, targetKeyType, hash)
|
||||
}
|
||||
|
||||
func (ke *KeyEntry) WrapKey(targetKey interface{}, targetKeyType KeyType, hash hash.Hash) (string, error) {
|
||||
func (ke *KeyEntry) WrapKey(targetKey any, targetKeyType KeyType, hash hash.Hash) (string, error) {
|
||||
// Presently this method implements a CKM_RSA_AES_KEY_WRAP-compatible
|
||||
// wrapping interface and only works on RSA keyEntries as a result.
|
||||
if ke.RSAPublicKey == nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
mathrand "math/rand"
|
||||
@@ -933,6 +934,25 @@ func autoVerify(depth int, t *testing.T, p *Policy, input []byte, sig *SigningRe
|
||||
}
|
||||
}
|
||||
|
||||
func autoVerifyDecrypt(depth int, t *testing.T, p *Policy, input []byte, ct string, factories ...any) {
|
||||
tabs := strings.Repeat("\t", depth)
|
||||
t.Log(tabs, "Automatically decrypting with options:", factories)
|
||||
|
||||
tabs = strings.Repeat("\t", depth+1)
|
||||
ptb64, err := p.DecryptWithFactory(nil, nil, ct, factories...)
|
||||
if err != nil {
|
||||
t.Fatal(tabs, "❌ Failed to automatically verify signature:", err)
|
||||
}
|
||||
|
||||
pt, err := base64.StdEncoding.DecodeString(ptb64)
|
||||
if err != nil {
|
||||
t.Fatal(tabs, "❌ Failed decoding plaintext:", err)
|
||||
}
|
||||
if !bytes.Equal(input, pt) {
|
||||
t.Fatal(tabs, "❌ Failed to automatically decrypt")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RSA_PSS(t *testing.T) {
|
||||
t.Log("Testing RSA PSS")
|
||||
mathrand.Seed(time.Now().UnixNano())
|
||||
@@ -1083,8 +1103,64 @@ func Test_RSA_PSS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RSA_PKCS1(t *testing.T) {
|
||||
t.Log("Testing RSA PKCS#1v1.5")
|
||||
func Test_RSA_PKCS1Encryption(t *testing.T) {
|
||||
t.Log("Testing RSA PKCS#1v1.5 padded encryption")
|
||||
|
||||
ctx := context.Background()
|
||||
storage := &logical.InmemStorage{}
|
||||
// https://crypto.stackexchange.com/a/1222
|
||||
pt := []byte("Sphinx of black quartz, judge my vow")
|
||||
input := base64.StdEncoding.EncodeToString(pt)
|
||||
|
||||
tabs := make(map[int]string)
|
||||
for i := 1; i <= 6; i++ {
|
||||
tabs[i] = strings.Repeat("\t", i)
|
||||
}
|
||||
|
||||
test_RSA_PKCS1 := func(t *testing.T, p *Policy, rsaKey *rsa.PrivateKey, padding PaddingScheme) {
|
||||
// 1. Make a signature with the given key size and hash algorithm.
|
||||
t.Log(tabs[3], "Make an automatic signature")
|
||||
ct, err := p.EncryptWithFactory(0, nil, nil, string(input), padding)
|
||||
if err != nil {
|
||||
t.Fatal(tabs[4], "❌ Failed to automatically encrypt:", err)
|
||||
}
|
||||
|
||||
// 1.1 Verify this signature using the *inferred* salt length.
|
||||
autoVerifyDecrypt(4, t, p, pt, ct, padding)
|
||||
}
|
||||
|
||||
rsaKeyTypes := []KeyType{KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096}
|
||||
testKeys, err := generateTestKeys()
|
||||
if err != nil {
|
||||
t.Fatalf("error generating test keys: %s", err)
|
||||
}
|
||||
|
||||
// 1. For each standard RSA key size 2048, 3072, and 4096...
|
||||
for _, rsaKeyType := range rsaKeyTypes {
|
||||
t.Log("Key size: ", rsaKeyType)
|
||||
p := &Policy{
|
||||
Name: fmt.Sprint(rsaKeyType), // NOTE: crucial to create a new key per key size
|
||||
Type: rsaKeyType,
|
||||
}
|
||||
|
||||
rsaKeyBytes := testKeys[rsaKeyType]
|
||||
err := p.Import(ctx, storage, rsaKeyBytes, rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(tabs[1], "❌ Failed to import key:", err)
|
||||
}
|
||||
rsaKeyAny, err := x509.ParsePKCS8PrivateKey(rsaKeyBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing test keys: %s", err)
|
||||
}
|
||||
rsaKey := rsaKeyAny.(*rsa.PrivateKey)
|
||||
for _, padding := range []PaddingScheme{PaddingScheme_OAEP, PaddingScheme_PKCS1v15, ""} {
|
||||
t.Run(fmt.Sprintf("%s/%s", rsaKeyType.String(), padding), func(t *testing.T) { test_RSA_PKCS1(t, p, rsaKey, padding) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RSA_PKCS1Signing(t *testing.T) {
|
||||
t.Log("Testing RSA PKCS#1v1.5 signatures")
|
||||
|
||||
ctx := context.Background()
|
||||
storage := &logical.InmemStorage{}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
@nonce={{this.props.nonce}}
|
||||
@bits={{this.props.bits}}
|
||||
@key_version={{this.props.key_version}}
|
||||
@padding_scheme={{this.props.padding_scheme}}
|
||||
@encodedBase64={{this.props.encodedBase64}}
|
||||
@toggleEncodeBase64={{this.toggleEncodeBase64}}
|
||||
@plaintext={{this.props.plaintext}}
|
||||
@@ -27,6 +28,7 @@
|
||||
@ciphertext={{this.props.ciphertext}}
|
||||
@context={{this.props.context}}
|
||||
@nonce={{this.props.nonce}}
|
||||
@padding_scheme={{this.props.padding_scheme}}
|
||||
@isModalActive={{this.isModalActive}}
|
||||
@plaintext={{this.props.plaintext}}
|
||||
@doSubmit={{perform this.doSubmit}}
|
||||
@@ -40,6 +42,7 @@
|
||||
@nonce={{this.props.nonce}}
|
||||
@bits={{this.props.bits}}
|
||||
@plaintext={{this.props.plaintext}}
|
||||
@padding_scheme={{this.props.padding_scheme}}
|
||||
@ciphertext={{this.props.ciphertext}}
|
||||
@doSubmit={{perform this.doSubmit}}
|
||||
@isModalActive={{this.isModalActive}}
|
||||
@@ -54,6 +57,8 @@
|
||||
@key_version={{this.props.key_version}}
|
||||
@ciphertext={{this.props.ciphertext}}
|
||||
@isModalActive={{this.isModalActive}}
|
||||
@decrypt_padding_scheme={{this.props.decrypt_padding_scheme}}
|
||||
@encrypt_padding_scheme={{this.props.encrypt_padding_scheme}}
|
||||
@doSubmit={{perform this.doSubmit}}
|
||||
data-test-transit-action={{@selectedAction}}
|
||||
/>
|
||||
|
||||
@@ -31,6 +31,9 @@ const STARTING_TRANSIT_PROPS = {
|
||||
hash_algorithm: 'sha2-256',
|
||||
algorithm: 'sha2-256',
|
||||
signature_algorithm: 'pss',
|
||||
padding_scheme: 'oaep',
|
||||
decrypt_padding_scheme: 'oaep',
|
||||
encrypt_padding_scheme: 'oaep',
|
||||
bits: 256,
|
||||
bytes: 32,
|
||||
ciphertext: null,
|
||||
@@ -59,12 +62,19 @@ const STARTING_TRANSIT_PROPS = {
|
||||
};
|
||||
|
||||
const PROPS_TO_KEEP = {
|
||||
encrypt: ['plaintext', 'context', 'nonce', 'key_version'],
|
||||
decrypt: ['ciphertext', 'context', 'nonce'],
|
||||
encrypt: ['plaintext', 'context', 'padding_scheme', 'nonce', 'key_version'],
|
||||
decrypt: ['ciphertext', 'context', 'padding_scheme', 'nonce'],
|
||||
sign: ['input', 'hash_algorithm', 'key_version', 'prehashed', 'signature_algorithm'],
|
||||
verify: ['input', 'hmac', 'signature', 'hash_algorithm', 'prehashed'],
|
||||
hmac: ['input', 'algorithm', 'key_version'],
|
||||
rewrap: ['ciphertext', 'context', 'nonce', 'key_version'],
|
||||
rewrap: [
|
||||
'ciphertext',
|
||||
'context',
|
||||
'decrypt_padding_scheme',
|
||||
'encrypt_padding_scheme',
|
||||
'nonce',
|
||||
'key_version',
|
||||
],
|
||||
datakey: [],
|
||||
};
|
||||
|
||||
@@ -182,6 +192,12 @@ export default class TransitKeyActions extends Component {
|
||||
formData.input = encodeString(formData.input);
|
||||
}
|
||||
}
|
||||
if (!this.keyIsRSA) {
|
||||
// Remove various rsa specific padding_scheme arguments if we aren't an RSA key
|
||||
delete formData.encrypt_padding_scheme;
|
||||
delete formData.decrypt_padding_scheme;
|
||||
delete formData.padding_scheme;
|
||||
}
|
||||
const payload = formData ? this.compactData(formData) : null;
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<form {{on "submit" (fn @doSubmit (hash param=@param context=@context nonce=@nonce bits=@bits))}} ...attributes>
|
||||
<form
|
||||
{{on "submit" (fn @doSubmit (hash param=@param context=@context padding_scheme=@padding_scheme nonce=@nonce bits=@bits))}}
|
||||
...attributes
|
||||
>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<NamespaceReminder @mode="perform" @noun="datakey creation" />
|
||||
<div class="content has-bottom-margin-l">
|
||||
@@ -65,6 +68,27 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}}
|
||||
<div class="field">
|
||||
<label for="padding_scheme" class="is-label">Padding scheme</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
name="padding_scheme"
|
||||
id="padding_scheme"
|
||||
data-test-padding-scheme
|
||||
onchange={{action (mut @padding_scheme) value="target.value"}}
|
||||
>
|
||||
{{#each (array "oaep" "pkcs1v15") as |scheme|}}
|
||||
<option selected={{eq @padding_scheme scheme}} value={{scheme}}>
|
||||
{{scheme}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<form {{on "submit" (fn @doSubmit (hash ciphertext=@ciphertext context=@context nonce=@nonce))}} ...attributes>
|
||||
<form
|
||||
{{on "submit" (fn @doSubmit (hash ciphertext=@ciphertext padding_scheme=@padding_scheme context=@context nonce=@nonce))}}
|
||||
...attributes
|
||||
>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<div class="content has-bottom-margin-l">
|
||||
<p>You can decrypt ciphertext using <code>{{@key.name}}</code> as the cryptographic key.</p>
|
||||
@@ -33,6 +36,27 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}}
|
||||
<div class="field">
|
||||
<label for="padding_scheme" class="is-label">Padding Scheme</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
name="padding_scheme"
|
||||
id="padding_scheme"
|
||||
data-test-padding-scheme
|
||||
onchange={{action (mut @padding_scheme) value="target.value"}}
|
||||
>
|
||||
{{#each (array "oaep" "pkcs1v15") as |scheme|}}
|
||||
<option selected={{eq @padding_scheme scheme}} value={{scheme}}>
|
||||
{{scheme}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq @key.convergentEncryptionVersion 1)}}
|
||||
<div class="field">
|
||||
<label for="nonce" class="is-label">Nonce</label>
|
||||
|
||||
@@ -4,7 +4,13 @@
|
||||
~}}
|
||||
|
||||
<form
|
||||
{{on "submit" (fn @doSubmit (hash plaintext=@plaintext context=@context nonce=@nonce key_version=@key_version))}}
|
||||
{{on
|
||||
"submit"
|
||||
(fn
|
||||
@doSubmit
|
||||
(hash plaintext=@plaintext padding_scheme=@padding_scheme context=@context nonce=@nonce key_version=@key_version)
|
||||
)
|
||||
}}
|
||||
...attributes
|
||||
>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
@@ -49,6 +55,27 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}}
|
||||
<div class="field">
|
||||
<label for="padding_scheme" class="is-label">Padding scheme</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
name="padding_scheme"
|
||||
id="padding_scheme"
|
||||
data-test-padding-scheme
|
||||
onchange={{action (mut @padding_scheme) value="target.value"}}
|
||||
>
|
||||
{{#each (array "oaep" "pkcs1v15") as |scheme|}}
|
||||
<option selected={{eq @padding_scheme scheme}} value={{scheme}}>
|
||||
{{scheme}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq @key.convergentEncryptionVersion 1)}}
|
||||
<div class="field">
|
||||
<div class="level">
|
||||
|
||||
@@ -4,7 +4,20 @@
|
||||
~}}
|
||||
|
||||
<form
|
||||
{{on "submit" (fn @doSubmit (hash ciphertext=@ciphertext context=@context nonce=@nonce key_version=@key_version))}}
|
||||
{{on
|
||||
"submit"
|
||||
(fn
|
||||
@doSubmit
|
||||
(hash
|
||||
ciphertext=@ciphertext
|
||||
decrypt_padding_scheme=@decrypt_padding_scheme
|
||||
encrypt_padding_scheme=@encrypt_padding_scheme
|
||||
context=@context
|
||||
nonce=@nonce
|
||||
key_version=@key_version
|
||||
)
|
||||
)
|
||||
}}
|
||||
...attributes
|
||||
>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
@@ -37,6 +50,42 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (includes @key.type (array "rsa-2048" "rsa-3072" "rsa-4096"))}}
|
||||
<div class="field">
|
||||
<label for="decrypt_padding_scheme" class="is-label">Decrypt padding scheme</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
name="decrypt_padding_scheme"
|
||||
id="decrypt_padding_scheme"
|
||||
data-test-padding-scheme="decrypt"
|
||||
onchange={{action (mut @decrypt_padding_scheme) value="target.value"}}
|
||||
>
|
||||
{{#each (array "oaep" "pkcs1v15") as |scheme|}}
|
||||
<option selected={{eq @decrypt_padding_scheme scheme}} value={{scheme}}>{{scheme}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="encrypt_padding_scheme" class="is-label">Encrypt padding scheme</label>
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth">
|
||||
<select
|
||||
name="encrypt_padding_scheme"
|
||||
id="encrypt_padding_scheme"
|
||||
data-test-padding-scheme="encrypt"
|
||||
onchange={{action (mut @encrypt_padding_scheme) value="target.value"}}
|
||||
>
|
||||
{{#each (array "oaep" "pkcs1v15") as |scheme|}}
|
||||
<option selected={{eq @encrypt_padding_scheme scheme}} value={{scheme}}>{{scheme}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if (eq @key.convergentEncryptionVersion 1)}}
|
||||
<div class="field">
|
||||
<label for="nonce" class="is-label">Nonce</label>
|
||||
|
||||
@@ -95,6 +95,50 @@ module('Integration | Component | transit key actions', function (hooks) {
|
||||
.exists({ count: 1 }, 'renders signature_algorithm field on verify with rsa key');
|
||||
});
|
||||
|
||||
test('it renders: padding_scheme field for rsa key types', async function (assert) {
|
||||
const supportedActions = ['datakey', 'decrypt', 'encrypt'];
|
||||
const supportedKeyTypes = ['rsa-2048', 'rsa-3072', 'rsa-4096'];
|
||||
|
||||
for (const key of supportedKeyTypes) {
|
||||
this.set('key', {
|
||||
type: key,
|
||||
backend: 'transit',
|
||||
supportedActions,
|
||||
});
|
||||
for (const action of this.key.supportedActions) {
|
||||
this.selectedAction = action;
|
||||
await render(hbs`
|
||||
<TransitKeyActions @selectedAction={{this.selectedAction}} @key={{this.key}} />`);
|
||||
assert
|
||||
.dom('[data-test-padding-scheme]')
|
||||
.hasValue(
|
||||
'oaep',
|
||||
`key type: ${key} renders padding_scheme field with default value for action: ${action}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
test('it renders: decrypt_padding_scheme and encrypt_padding_scheme fields for rsa key types', async function (assert) {
|
||||
this.selectedAction = 'rewrap';
|
||||
const supportedKeyTypes = ['rsa-2048', 'rsa-3072', 'rsa-4096'];
|
||||
const SELECTOR = (type) => `[data-test-padding-scheme="${type}"]`;
|
||||
for (const key of supportedKeyTypes) {
|
||||
this.set('key', {
|
||||
type: key,
|
||||
backend: 'transit',
|
||||
supportedActions: [this.selectedAction],
|
||||
});
|
||||
await render(hbs`
|
||||
<TransitKeyActions @selectedAction={{this.selectedAction}} @key={{this.key}} />`);
|
||||
assert
|
||||
.dom(SELECTOR('encrypt'))
|
||||
.hasValue('oaep', `key type: ${key} renders ${SELECTOR('encrypt')} field with default value`);
|
||||
assert
|
||||
.dom(SELECTOR('decrypt'))
|
||||
.hasValue('oaep', `key type: ${key} renders ${SELECTOR('decrypt')} field with default value`);
|
||||
}
|
||||
});
|
||||
|
||||
async function doEncrypt(assert, actions = [], keyattrs = {}) {
|
||||
const keyDefaults = { backend: 'transit', id: 'akey', supportedActions: ['encrypt'].concat(actions) };
|
||||
|
||||
|
||||
@@ -797,6 +797,16 @@ will be returned.
|
||||
data (also known as additional data or AAD) to also be authenticated with
|
||||
AEAD ciphers (`aes128-gcm96`, `aes256-gcm`, and `chacha20-poly1305`).
|
||||
|
||||
- `padding_scheme` `(string: "oaep")` – Specifies the RSA encryption padding
|
||||
scheme for RSA keys. Must be one of the following supported signature types:
|
||||
|
||||
- `oaep`
|
||||
- `pkcs1v15`
|
||||
|
||||
~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses.
|
||||
It is recommended that the default of OAEP be used unless specific backwards
|
||||
compatibility is required.
|
||||
|
||||
- `context` `(string: "")` – Specifies the **base64 encoded** context for key
|
||||
derivation. This is required if key derivation is enabled for this key.
|
||||
|
||||
@@ -922,6 +932,12 @@ This endpoint decrypts the provided ciphertext using the named key.
|
||||
data (also known as additional data or AAD) to also be authenticated with
|
||||
AEAD ciphers (`aes128-gcm96`, `aes256-gcm`, and `chacha20-poly1305`).
|
||||
|
||||
- `padding_scheme` `(string: "oaep")` – Specifies the RSA decryption padding
|
||||
scheme for RSA keys. Must be one of the following supported signature types:
|
||||
|
||||
- `oaep`
|
||||
- `pkcs1v15`
|
||||
|
||||
- `context` `(string: "")` – Specifies the **base64 encoded** context for key
|
||||
derivation. This is required if key derivation is enabled.
|
||||
|
||||
@@ -1008,6 +1024,22 @@ functionality to untrusted users or scripts.
|
||||
|
||||
- `ciphertext` `(string: <required>)` – Specifies the ciphertext to re-encrypt.
|
||||
|
||||
- `decrypt_padding_scheme` `(string: "oaep")` – Specifies the RSA padding
|
||||
scheme for RSA keys for the decrypt step. Must be one of the following supported signature types:
|
||||
|
||||
- `oaep`
|
||||
- `pkcs1v15`
|
||||
|
||||
- `encrypt_padding_scheme` `(string: "oaep")` – Specifies the RSA padding
|
||||
scheme for RSA keys for the encrypt step. Must be one of the following supported signature types:
|
||||
|
||||
- `oaep`
|
||||
- `pkcs1v15`
|
||||
|
||||
~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses.
|
||||
It is recommended that the default of OAEP be used unless specific backwards
|
||||
compatibility is required.
|
||||
|
||||
- `context` `(string: "")` – Specifies the **base64 encoded** context for key
|
||||
derivation. This is required if key derivation is enabled.
|
||||
|
||||
@@ -1109,6 +1141,16 @@ then made available to trusted users.
|
||||
- `bits` `(int: 256)` – Specifies the number of bits in the desired key. Can be
|
||||
128, 256, or 512.
|
||||
|
||||
- `padding_scheme` `(string: "oaep")` – Specifies the RSA encryption padding
|
||||
scheme for RSA keys. Must be one of the following supported signature types:
|
||||
|
||||
- `oaep`
|
||||
- `pkcs1v15`
|
||||
|
||||
~> **Warning:** `pkcs1v15` is a legacy padding scheme with security weaknesses.
|
||||
It is recommended that the default of OAEP be used unless specific backwards
|
||||
compatibility is required.
|
||||
|
||||
### Sample payload
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user