mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
Fail sign/verify apis when Ed25519ph/ctx arguments are provided on CE (#28838)
This commit is contained in:
@@ -120,40 +120,7 @@ func (b *backend) pathSign() *framework.Path {
|
||||
OperationSuffix: "|with-algorithm",
|
||||
},
|
||||
|
||||
Fields: pathSignFieldArgs(),
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathSignWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathSignHelpSyn,
|
||||
HelpDescription: pathSignHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) pathVerify() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "verify/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
|
||||
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: operationPrefixTransit,
|
||||
OperationVerb: "verify",
|
||||
OperationSuffix: "|with-algorithm",
|
||||
},
|
||||
|
||||
Fields: pathVerifyFieldArgs(),
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathVerifyWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathVerifyHelpSyn,
|
||||
HelpDescription: pathVerifyHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func pathSignFieldArgs() map[string]*framework.FieldSchema {
|
||||
fields := map[string]*framework.FieldSchema{
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The key to use",
|
||||
@@ -170,6 +137,12 @@ func pathSignFieldArgs() map[string]*framework.FieldSchema {
|
||||
derivation is enabled; currently only available with ed25519 keys.`,
|
||||
},
|
||||
|
||||
"signature_context": {
|
||||
Type: framework.TypeString,
|
||||
Description: `Base64 encoded context for Ed25519ph and Ed25519ctx signatures.
|
||||
Currently only available with Ed25519 keys. (Enterprise Only)`,
|
||||
},
|
||||
|
||||
"hash_algorithm": {
|
||||
Type: framework.TypeString,
|
||||
Default: defaultHashAlgorithm,
|
||||
@@ -241,14 +214,28 @@ any supplied 'input' or 'context' parameters will be ignored. Responses are retu
|
||||
'batch_results' array component of the 'data' element of the response. Any batch output will
|
||||
preserve the order of the batch input`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathSignWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathSignHelpSyn,
|
||||
HelpDescription: pathSignHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
addEntSignFieldArgs(fields)
|
||||
return fields
|
||||
}
|
||||
func (b *backend) pathVerify() *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "verify/" + framework.GenericNameRegex("name") + framework.OptionalParamRegex("urlalgorithm"),
|
||||
|
||||
func pathVerifyFieldArgs() map[string]*framework.FieldSchema {
|
||||
fields := map[string]*framework.FieldSchema{
|
||||
DisplayAttrs: &framework.DisplayAttributes{
|
||||
OperationPrefix: operationPrefixTransit,
|
||||
OperationVerb: "verify",
|
||||
OperationSuffix: "|with-algorithm",
|
||||
},
|
||||
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"name": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The key to use",
|
||||
@@ -260,6 +247,12 @@ func pathVerifyFieldArgs() map[string]*framework.FieldSchema {
|
||||
derivation is enabled; currently only available with ed25519 keys.`,
|
||||
},
|
||||
|
||||
"signature_context": {
|
||||
Type: framework.TypeString,
|
||||
Description: `Base64 encoded context for Ed25519ph and Ed25519ctx signatures.
|
||||
Currently only available with Ed25519 keys. (Enterprise Only)`,
|
||||
},
|
||||
|
||||
"signature": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The signature, including vault header/key version",
|
||||
@@ -270,6 +263,11 @@ derivation is enabled; currently only available with ed25519 keys.`,
|
||||
Description: "The HMAC, including vault header/key version",
|
||||
},
|
||||
|
||||
"cmac": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The CMAC, including vault header/key version (Enterprise only)",
|
||||
},
|
||||
|
||||
"input": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The base64-encoded input data to verify",
|
||||
@@ -342,10 +340,15 @@ any supplied 'input', 'hmac', 'cmac' or 'signature' parameters will be ignored.
|
||||
'batch_results' array component of the 'data' element of the response. Any batch output will
|
||||
preserve the order of the batch input`,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addEntVerifyFieldArgs(fields)
|
||||
return fields
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.UpdateOperation: b.pathVerifyWrite,
|
||||
},
|
||||
|
||||
HelpSynopsis: pathVerifyHelpSyn,
|
||||
HelpDescription: pathVerifyHelpDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func getSaltLength(d *framework.FieldData) (int, error) {
|
||||
@@ -527,7 +530,7 @@ func (b *backend) getPolicySignArgs(ctx context.Context, p *keysutil.Policy, arg
|
||||
}
|
||||
|
||||
if err := b.populateEntPolicySigningOptions(ctx, p, args, item, &psa); err != nil {
|
||||
return policySignArgs{}, nil
|
||||
return policySignArgs{}, fmt.Errorf("failed to parse batch item: %s", err)
|
||||
}
|
||||
return psa, nil
|
||||
}
|
||||
|
||||
@@ -9,20 +9,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
||||
)
|
||||
|
||||
// addEntSignFieldArgs adds the enterprise only fields to the field schema definition
|
||||
func addEntSignFieldArgs(_ map[string]*framework.FieldSchema) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// addEntVerifyFieldArgs adds the enterprise only fields to the field schema definition
|
||||
func addEntVerifyFieldArgs(_ map[string]*framework.FieldSchema) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
// validateSignApiArgsVersionSpecific will perform a validation of the Sign API parameters
|
||||
// from the Enterprise or CE point of view.
|
||||
func validateSignApiArgsVersionSpecific(p *keysutil.Policy, apiArgs commonSignVerifyApiArgs) error {
|
||||
@@ -41,13 +30,44 @@ func validateSignApiArgsVersionSpecific(p *keysutil.Policy, apiArgs commonSignVe
|
||||
|
||||
// populateEntPolicySigning augments or tweaks the input parameters to the SDK policy.SignWithOptions for
|
||||
// Enterprise usage.
|
||||
func (b *backend) populateEntPolicySigningOptions(_ context.Context, _ *keysutil.Policy, _ signApiArgs, _ batchRequestSignItem, _ *policySignArgs) error {
|
||||
return nil
|
||||
func (b *backend) populateEntPolicySigningOptions(_ context.Context, p *keysutil.Policy, args signApiArgs, item batchRequestSignItem, _ *policySignArgs) error {
|
||||
return _forbidEd25519EntBehavior(p, args.commonSignVerifyApiArgs, item["signature_context"])
|
||||
}
|
||||
|
||||
// populateEntPolicyVerifyOptions augments or tweaks the input parameters to the SDK policy.VerifyWithOptions for
|
||||
// Enterprise usage.
|
||||
func (b *backend) populateEntPolicyVerifyOptions(ctx context.Context, p *keysutil.Policy, args verifyApiArgs, item batchRequestVerifyItem, vsa *policyVerifyArgs) error {
|
||||
func (b *backend) populateEntPolicyVerifyOptions(_ context.Context, p *keysutil.Policy, args verifyApiArgs, item batchRequestVerifyItem, _ *policyVerifyArgs) error {
|
||||
sigContext, err := _validateString(item, "signature_context")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _forbidEd25519EntBehavior(p, args.commonSignVerifyApiArgs, sigContext)
|
||||
}
|
||||
|
||||
func _validateString(item batchRequestVerifyItem, key string) (string, error) {
|
||||
if itemVal, exists := item[key]; exists {
|
||||
if itemStrVal, ok := itemVal.(string); ok {
|
||||
return itemStrVal, nil
|
||||
}
|
||||
return "", fmt.Errorf("expected string for key=%q, got=%q", key, itemVal)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func _forbidEd25519EntBehavior(p *keysutil.Policy, apiArgs commonSignVerifyApiArgs, sigContext string) error {
|
||||
if p.Type != keysutil.KeyType_ED25519 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case apiArgs.prehashed:
|
||||
return fmt.Errorf("only Pure Ed25519 signatures supported, prehashed must be false")
|
||||
case apiArgs.hashAlgorithm == keysutil.HashTypeSHA2512:
|
||||
return fmt.Errorf("only Pure Ed25519 signatures supported, hash_alogithm should not be set")
|
||||
case sigContext != "":
|
||||
return fmt.Errorf("only Pure Ed25519 signatures supported, signature_context must be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"github.com/hashicorp/vault/sdk/helper/keysutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
)
|
||||
|
||||
@@ -368,6 +370,103 @@ func validatePublicKey(t *testing.T, in string, sig string, pubKeyRaw []byte, ex
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransit_SignVerify_Ed25519Behavior makes sure the options on ENT for a
|
||||
// Ed25519ph/ctx signature fail on CE and ENT if invalid
|
||||
func TestTransit_SignVerify_Ed25519Behavior(t *testing.T) {
|
||||
b, storage := createBackendWithSysView(t)
|
||||
|
||||
// First create a key
|
||||
req := &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/foo",
|
||||
Data: map[string]interface{}{
|
||||
"type": "ed25519",
|
||||
},
|
||||
}
|
||||
_, err := b.HandleRequest(context.Background(), req)
|
||||
require.NoError(t, err, "failed creating ed25519 key")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args map[string]interface{}
|
||||
worksOnEnt bool
|
||||
}{
|
||||
{"sha2-512 only", map[string]interface{}{"hash_algorithm": "sha2-512"}, false},
|
||||
{"prehashed only", map[string]interface{}{"prehashed": "true"}, false},
|
||||
{"incorrect input for ph args", map[string]interface{}{"prehashed": "true", "hash_algorithm": "sha2-512"}, false},
|
||||
{"context too long", map[string]interface{}{"signature_context": strings.Repeat("x", 1024)}, false},
|
||||
{
|
||||
name: "ctx-signature",
|
||||
args: map[string]interface{}{
|
||||
"signature_context": "dGVzdGluZyBjb250ZXh0Cg==",
|
||||
},
|
||||
worksOnEnt: true,
|
||||
},
|
||||
{
|
||||
name: "ph-signature",
|
||||
args: map[string]interface{}{
|
||||
"input": "3a81oZNherrMQXNJriBBMRLm+k6JqX6iCp7u5ktV05ohkpkqJ0/BqDa6PCOj/uu9RU1EI2Q86A4qmslPpUyknw==",
|
||||
"prehashed": "true",
|
||||
"hash_algorithm": "sha2-512",
|
||||
"signature_context": "dGVzdGluZyBjb250ZXh0Cg==",
|
||||
},
|
||||
worksOnEnt: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
signData := map[string]interface{}{"input": "dGhlIHF1aWNrIGJyb3duIGZveA=="}
|
||||
|
||||
// if tc.args specifies input, this should overwrite our static value above.
|
||||
maps.Copy(signData, tc.args)
|
||||
|
||||
req = &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "sign/foo",
|
||||
Data: signData,
|
||||
}
|
||||
|
||||
signSignature := "YmFkIHNpZ25hdHVyZQo=" // "bad signature" but is overwritten if sign works
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if constants.IsEnterprise && tc.worksOnEnt {
|
||||
require.NoError(t, err, "expected sign to work on ENT but failed: resp: %v", resp)
|
||||
require.NotNil(t, resp, "sign should have had non-nil response on ENT")
|
||||
require.False(t, resp.IsError(), "sign expected to work on ENT but failed")
|
||||
signSignature = resp.Data["signature"].(string)
|
||||
require.NotEmpty(t, signSignature, "sign expected to work on ENT but was empty")
|
||||
} else {
|
||||
require.ErrorContains(t, err, "invalid request", "expected sign request to fail with invalid request")
|
||||
}
|
||||
|
||||
verifyData := map[string]interface{}{
|
||||
"input": signData["input"],
|
||||
"signature": signSignature,
|
||||
}
|
||||
|
||||
// if tc.args specifies input, this should overwrite our static value above.
|
||||
maps.Copy(verifyData, tc.args)
|
||||
|
||||
req = &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "verify/foo",
|
||||
Data: verifyData,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
if constants.IsEnterprise && tc.worksOnEnt {
|
||||
require.NoError(t, err, "verify expected to work on ENT but failed: resp: %v", resp)
|
||||
require.NotNil(t, resp, "verify should have had non-nil response on ENT")
|
||||
require.False(t, resp.IsError(), "expected verify to work on ENT but failed")
|
||||
require.True(t, resp.Data["valid"].(bool), "signature verification should have worked")
|
||||
} else {
|
||||
require.ErrorContains(t, err, "invalid request", "expected verify request to fail with invalid request")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransit_SignVerify_ED25519(t *testing.T) {
|
||||
b, storage := createBackendWithSysView(t)
|
||||
|
||||
|
||||
@@ -90,6 +90,20 @@ const (
|
||||
PaddingScheme_PKCS1v15 = PaddingScheme("pkcs1v15")
|
||||
)
|
||||
|
||||
var genEd25519Options = func(hashAlgorithm HashType, signatureContext string) (*stdlibEd25519.Options, error) {
|
||||
if signatureContext != "" {
|
||||
return nil, fmt.Errorf("signature context is not supported feature")
|
||||
}
|
||||
|
||||
if hashAlgorithm == HashTypeSHA2512 {
|
||||
return nil, fmt.Errorf("hash algorithm of SHA2 512 is not supported feature")
|
||||
}
|
||||
|
||||
return &stdlibEd25519.Options{
|
||||
Hash: crypto.Hash(0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p PaddingScheme) String() string {
|
||||
return string(p)
|
||||
}
|
||||
@@ -1302,11 +1316,11 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin
|
||||
key = ed25519.PrivateKey(keyParams.Key)
|
||||
}
|
||||
|
||||
opts := genEd25519Options(hashAlgorithm, options.SigContext)
|
||||
opts, err := genEd25519Options(hashAlgorithm, options.SigContext)
|
||||
if err != nil {
|
||||
return nil, errutil.UserError{Err: fmt.Sprintf("error generating Ed25519 options: %v", err)}
|
||||
}
|
||||
|
||||
// Per docs, do not pre-hash ed25519; it does two passes and performs
|
||||
// its own hashing when we specify crypto.Hash(0). Ed25519Ph assumes
|
||||
// pre-hashed with SHA512
|
||||
sig, err = key.Sign(rand.Reader, input, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1503,7 +1517,10 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o
|
||||
pub = ed25519.PublicKey(raw)
|
||||
}
|
||||
|
||||
opts := genEd25519Options(hashAlgorithm, options.SigContext)
|
||||
opts, err := genEd25519Options(hashAlgorithm, options.SigContext)
|
||||
if err != nil {
|
||||
return false, errutil.UserError{Err: fmt.Sprintf("error generating Ed25519 options: %v", err)}
|
||||
}
|
||||
if err := stdlibEd25519.VerifyWithOptions(pub, input, sigBytes, opts); err != nil {
|
||||
// We drop the error, just report back that we failed signature verification
|
||||
return false, nil
|
||||
@@ -1561,20 +1578,6 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o
|
||||
}
|
||||
}
|
||||
|
||||
func genEd25519Options(hashAlgorithm HashType, sigContext string) *stdlibEd25519.Options {
|
||||
opts := &stdlibEd25519.Options{
|
||||
Hash: crypto.Hash(0),
|
||||
}
|
||||
if hashAlgorithm == HashTypeSHA2512 {
|
||||
// activate ph mode, we assume input is prehashed
|
||||
opts.Hash = crypto.SHA512
|
||||
}
|
||||
if len(sigContext) > 0 {
|
||||
opts.Context = sigContext
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte, randReader io.Reader) error {
|
||||
return p.ImportPublicOrPrivate(ctx, storage, key, true, randReader)
|
||||
}
|
||||
|
||||
@@ -1480,6 +1480,9 @@ supports signing.
|
||||
signature rather than the `PKCSv1_5_DERnull` signature type usually
|
||||
created. See [RFC 3447 Section 9.2](https://www.rfc-editor.org/rfc/rfc3447#section-9.2).
|
||||
|
||||
~> **Note**: using `hash_algorithm=sha2-512` requires setting `prehashed=true`
|
||||
for Ed25519 backed keys which enabled Ed25519ph signature support on Enterprise.
|
||||
|
||||
- `input` `(string: "")` – Specifies the **base64 encoded** input data. One of
|
||||
`input` or `batch_input` must be supplied.
|
||||
|
||||
@@ -1526,9 +1529,9 @@ supports signing.
|
||||
data you want signed, when set, `input` is expected to be base64-encoded
|
||||
binary hashed data, not hex-formatted. (As an example, on the command line,
|
||||
you could generate a suitable input via `openssl dgst -sha256 -binary | base64`.)
|
||||
On Enterprise <EnterpriseAlert inline="true" />, enabling this will activate
|
||||
Ed25519ph signatures for Ed25519 keys along with hash_algorithm being either `none`
|
||||
or `sha2-512`.
|
||||
On Enterprise <EnterpriseAlert inline="true" />, enabling this along with
|
||||
hash_algorithm being set to `sha2-512` will activate Ed25519ph signatures for
|
||||
Ed25519 keys
|
||||
|
||||
- `signature_algorithm` `(string: "pss")` – When using a RSA key, specifies the RSA
|
||||
signature algorithm to use for signing. Supported signature types are:
|
||||
@@ -1669,6 +1672,9 @@ or [generate CMAC](#generate-cmac) API calls.
|
||||
signature rather than the `PKCSv1_5_DERnull` signature type usually
|
||||
verified. See [RFC 3447 Section 9.2](https://www.rfc-editor.org/rfc/rfc3447#section-9.2).
|
||||
|
||||
~> **Note**: using `hash_algorithm=sha2-512` requires setting `prehashed=true`
|
||||
for Ed25519 backed keys which enabled Ed25519ph signature support on Enterprise.
|
||||
|
||||
- `input` `(string: "")` – Specifies the **base64 encoded** input data. One of
|
||||
`input` or `batch_input` must be supplied.
|
||||
|
||||
@@ -1730,9 +1736,9 @@ or [generate CMAC](#generate-cmac) API calls.
|
||||
data you want signed, when set, `input` is expected to be base64-encoded
|
||||
binary hashed data, not hex-formatted. (As an example, on the command line,
|
||||
you could generate a suitable input via `openssl dgst -sha256 -binary | base64`.)
|
||||
On Enterprise <EnterpriseAlert inline="true" />, enabling this will activate
|
||||
Ed25519ph signatures for Ed25519 keys along with hash_algorithm being either `none`
|
||||
or `sha2-512`.
|
||||
On Enterprise <EnterpriseAlert inline="true" />, enabling this along with
|
||||
hash_algorithm being set to `sha2-512` will activate Ed25519ph signatures for
|
||||
Ed25519 keys
|
||||
|
||||
- `signature_algorithm` `(string: "pss")` – When using a RSA key, specifies the RSA
|
||||
signature algorithm to use for signature verification. Supported signature types
|
||||
|
||||
Reference in New Issue
Block a user