Skip to content

Commit

Permalink
Add ml-dsa key type and stubs (#28961)
Browse files Browse the repository at this point in the history
* add ml-dsa key type and stubs

* add in sdk changes

* ent breakout

* fix private key func and run go mod tidy

* change function name

* tidy go.mod

---------

Co-authored-by: Scott G. Miller <[email protected]>
  • Loading branch information
rculpepper and sgmiller authored Nov 20, 2024
1 parent 1a15c4b commit a0ceaf6
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 31 deletions.
9 changes: 8 additions & 1 deletion builtin/logical/transit/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ const (
minCacheSize = 10
)

var ErrCmacEntOnly = errors.New("CMAC operations are only available in enterprise versions of Vault")
var (
ErrCmacEntOnly = errors.New("CMAC operations are only available in enterprise versions of Vault")
ErrPQCEntOnly = errors.New("PQC key types are only available in enterprise versions of Vault")
)

func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b, err := Backend(ctx, conf)
Expand Down Expand Up @@ -183,6 +186,10 @@ func (b *backend) GetPolicy(ctx context.Context, polReq keysutil.PolicyRequest,
return nil, false, ErrCmacEntOnly
}

if p != nil && p.Type.IsPQC() && !constants.IsEnterprise {
return nil, false, ErrPQCEntOnly
}

return p, true, nil
}

Expand Down
29 changes: 27 additions & 2 deletions builtin/logical/transit/path_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (b *backend) pathKeys() *framework.Path {
Description: `
The type of key to create. Currently, "aes128-gcm96" (symmetric), "aes256-gcm96" (symmetric), "ecdsa-p256"
(asymmetric), "ecdsa-p384" (asymmetric), "ecdsa-p521" (asymmetric), "ed25519" (asymmetric), "rsa-2048" (asymmetric), "rsa-3072"
(asymmetric), "rsa-4096" (asymmetric) are supported. Defaults to "aes256-gcm96".
(asymmetric), "rsa-4096" (asymmetric), "ml-dsa" (asymmetric) are supported. Defaults to "aes256-gcm96".
`,
},

Expand Down Expand Up @@ -130,6 +130,11 @@ key.`,
Type: framework.TypeString,
Description: "The UUID of the managed key to use for this transit key",
},
"parameter_set": {
Type: framework.TypeString,
Description: `The parameter set to use. Applies to ML-DSA and SLH-DSA key types.
For ML-DSA key types, valid values are 44, 65, or 87.`,
},
},

Operations: map[logical.Operation]framework.OperationHandler{
Expand Down Expand Up @@ -178,6 +183,7 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
autoRotatePeriod := time.Second * time.Duration(d.Get("auto_rotate_period").(int))
managedKeyName := d.Get("managed_key_name").(string)
managedKeyId := d.Get("managed_key_id").(string)
parameterSet := d.Get("parameter_set").(string)

if autoRotatePeriod != 0 && autoRotatePeriod < time.Hour {
return logical.ErrorResponse("auto rotate period must be 0 to disable or at least an hour"), nil
Expand Down Expand Up @@ -227,6 +233,15 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
polReq.KeyType = keysutil.KeyType_AES128_CMAC
case "aes256-cmac":
polReq.KeyType = keysutil.KeyType_AES256_CMAC
case "ml-dsa":
polReq.KeyType = keysutil.KeyType_ML_DSA
if parameterSet != keysutil.ParameterSet_ML_DSA_44 &&
parameterSet != keysutil.ParameterSet_ML_DSA_65 &&
parameterSet != keysutil.ParameterSet_ML_DSA_87 {
return logical.ErrorResponse(fmt.Sprintf("invalid parameter set %s for key type %s", parameterSet, keyType)), logical.ErrInvalidRequest
}

polReq.ParameterSet = parameterSet
default:
return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
}
Expand All @@ -253,6 +268,10 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
return logical.ErrorResponse(ErrCmacEntOnly.Error()), logical.ErrInvalidRequest
}

if polReq.KeyType.IsPQC() && !constants.IsEnterprise {
return logical.ErrorResponse(ErrPQCEntOnly.Error()), logical.ErrInvalidRequest
}

p, upserted, err := b.GetPolicy(ctx, polReq, b.GetRandomReader())
if err != nil {
return nil, err
Expand Down Expand Up @@ -370,6 +389,10 @@ func (b *backend) formatKeyPolicy(p *keysutil.Policy, context []byte) (*logical.
}
}

if p.ParameterSet != "" {
resp.Data["parameter_set"] = p.ParameterSet
}

switch p.Type {
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305:
retKeys := map[string]int64{}
Expand All @@ -378,7 +401,7 @@ func (b *backend) formatKeyPolicy(p *keysutil.Policy, context []byte) (*logical.
}
resp.Data["keys"] = retKeys

case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ECDSA_P384, keysutil.KeyType_ECDSA_P521, keysutil.KeyType_ED25519, keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096:
case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ECDSA_P384, keysutil.KeyType_ECDSA_P521, keysutil.KeyType_ED25519, keysutil.KeyType_RSA2048, keysutil.KeyType_RSA3072, keysutil.KeyType_RSA4096, keysutil.KeyType_ML_DSA:
retKeys := map[string]map[string]interface{}{}
for k, v := range p.Keys {
key := asymKey{
Expand Down Expand Up @@ -441,6 +464,8 @@ func (b *backend) formatKeyPolicy(p *keysutil.Policy, context []byte) (*logical.
return nil, err
}
key.PublicKey = pubKey
case keysutil.KeyType_ML_DSA:
key.Name = "ml-dsa-" + p.ParameterSet
}

retKeys[k] = structs.New(key).Map()
Expand Down
12 changes: 12 additions & 0 deletions builtin/logical/transit/path_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ func TestTransit_CreateKey(t *testing.T) {
creationParams: map[string]interface{}{"type": "aes256-cmac"},
entOnly: true,
},
"ML-DSA-44": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "44"},
entOnly: true,
},
"ML-DSA-65": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "65"},
entOnly: true,
},
"ML-DSA-87": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "87"},
entOnly: true,
},
"bad key type": {
creationParams: map[string]interface{}{"type": "fake-key-type"},
shouldError: true,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ require (
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 // indirect
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
github.com/containerd/continuity v0.4.3 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -898,8 +898,8 @@ github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQ
github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 h1:ef0OsiQjSQggHrLFAMDRiu6DfkVSElA5jfG1/Nkyu6c=
github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1/go.mod h1:sgaEj3tRn0hwe7GPdEUwxrdOqjBzyjyvyOCGf1OQyZY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
Expand Down
10 changes: 10 additions & 0 deletions sdk/helper/keysutil/lock_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type PolicyRequest struct {

// The UUID of the managed key, if using one
ManagedKeyUUID string

// ParameterSet indicates the parameter set to use with ML-DSA and SLH-DSA keys
ParameterSet string
}

type LockManager struct {
Expand Down Expand Up @@ -403,6 +406,12 @@ func (lm *LockManager) GetPolicy(ctx context.Context, req PolicyRequest, rand io
return nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType)
}

case KeyType_ML_DSA:
if req.Derived || req.Convergent {
cleanup()
return nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", req.KeyType)
}

default:
cleanup()
return nil, false, fmt.Errorf("unsupported key type %v", req.KeyType)
Expand All @@ -417,6 +426,7 @@ func (lm *LockManager) GetPolicy(ctx context.Context, req PolicyRequest, rand io
AllowPlaintextBackup: req.AllowPlaintextBackup,
AutoRotatePeriod: req.AutoRotatePeriod,
KeySize: req.KeySize,
ParameterSet: req.ParameterSet,
}

if req.Derived {
Expand Down
60 changes: 36 additions & 24 deletions sdk/helper/keysutil/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,16 @@ const (
KeyType_HMAC
KeyType_AES128_CMAC
KeyType_AES256_CMAC
KeyType_ML_DSA
// If adding to this list please update allTestKeyTypes in policy_test.go
)

const (
ParameterSet_ML_DSA_44 = "44"
ParameterSet_ML_DSA_65 = "65"
ParameterSet_ML_DSA_87 = "87"
)

const (
// ErrTooOld is returned whtn the ciphertext or signatures's key version is
// too old.
Expand Down Expand Up @@ -181,7 +188,7 @@ func (kt KeyType) DecryptionSupported() bool {

func (kt KeyType) SigningSupported() bool {
switch kt {
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_ED25519, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096, KeyType_MANAGED_KEY:
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_ED25519, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096, KeyType_MANAGED_KEY, KeyType_ML_DSA:
return true
}
return false
Expand Down Expand Up @@ -231,6 +238,15 @@ func (kt KeyType) HMACSupported() bool {
}
}

func (kt KeyType) IsPQC() bool {
switch kt {
case KeyType_ML_DSA:
return true
default:
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, KeyType_ED25519:
Expand Down Expand Up @@ -278,6 +294,8 @@ func (kt KeyType) String() string {
return "aes128-cmac"
case KeyType_AES256_CMAC:
return "aes256-cmac"
case KeyType_ML_DSA:
return "ml-dsa"
}

return "[unknown]"
Expand All @@ -290,6 +308,8 @@ type KeyData struct {

// KeyEntry stores the key and metadata
type KeyEntry struct {
entKeyEntry

// AES or some other kind that is a pure byte slice like ED25519
Key []byte `json:"key"`

Expand Down Expand Up @@ -325,7 +345,7 @@ type KeyEntry struct {
}

func (ke *KeyEntry) IsPrivateKeyMissing() bool {
if ke.RSAKey != nil || ke.EC_D != nil || len(ke.Key) != 0 || len(ke.ManagedKeyUUID) != 0 {
if ke.RSAKey != nil || ke.EC_D != nil || len(ke.Key) != 0 || len(ke.ManagedKeyUUID) != 0 || !ke.IsEntPrivateKeyMissing() {
return false
}

Expand Down Expand Up @@ -395,6 +415,9 @@ type PolicyConfig struct {
// StoragePrefix is used to add a prefix when storing and retrieving the
// policy object.
StoragePrefix string

// ParameterSet indicates the parameter set to use with ML-DSA and SLH-DSA keys
ParameterSet string
}

// NewPolicy takes a policy config and returns a Policy with those settings.
Expand All @@ -412,6 +435,7 @@ func NewPolicy(config PolicyConfig) *Policy {
AllowPlaintextBackup: config.AllowPlaintextBackup,
VersionTemplate: config.VersionTemplate,
StoragePrefix: config.StoragePrefix,
ParameterSet: config.ParameterSet,
}
}

Expand Down Expand Up @@ -542,6 +566,9 @@ type Policy struct {

// AllowImportedKeyRotation indicates whether an imported key may be rotated by Vault
AllowImportedKeyRotation bool

// ParameterSet indicates the parameter set to use with ML-DSA and SLH-DSA keys
ParameterSet string
}

func (p *Policy) Lock(exclusive bool) {
Expand Down Expand Up @@ -1355,20 +1382,8 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin
default:
return nil, errutil.InternalError{Err: fmt.Sprintf("unsupported rsa signature algorithm %s", sigAlgorithm)}
}

case KeyType_MANAGED_KEY:
keyEntry, err := p.safeGetKeyEntry(ver)
if err != nil {
return nil, err
}

sig, err = p.signWithManagedKey(options, keyEntry, input)
if err != nil {
return nil, err
}

default:
return nil, fmt.Errorf("unsupported key type %v", p.Type)
sig, err = entSignWithOptions(p, input, ver, options)
}

// Convert to base64
Expand Down Expand Up @@ -1565,16 +1580,8 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o

return err == nil, nil

case KeyType_MANAGED_KEY:
keyEntry, err := p.safeGetKeyEntry(ver)
if err != nil {
return false, err
}

return p.verifyWithManagedKey(options, keyEntry, input, sigBytes)

default:
return false, errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
return entVerifySignatureWithOptions(p, input, sigBytes, ver, options)
}
}

Expand Down Expand Up @@ -1824,6 +1831,11 @@ func (p *Policy) RotateInMemory(randReader io.Reader) (retErr error) {
}

entry.RSAPublicKey = entry.RSAKey.Public().(*rsa.PublicKey)

default:
if err := entRotateInMemory(p, &entry, randReader); err != nil {
return err
}
}

if p.ConvergentEncryption {
Expand Down
31 changes: 31 additions & 0 deletions sdk/helper/keysutil/policy_ce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build !enterprise

package keysutil

import (
"fmt"
"io"

"github.com/hashicorp/vault/sdk/helper/errutil"
)

type entKeyEntry struct{}

func (e entKeyEntry) IsEntPrivateKeyMissing() bool {
return true
}

func entSignWithOptions(p *Policy, input []byte, ver int, options *SigningOptions) ([]byte, error) {
return nil, fmt.Errorf("unsupported key type %v", p.Type)
}

func entVerifySignatureWithOptions(p *Policy, input []byte, sigBytes []byte, ver int, options *SigningOptions) (bool, error) {
return false, errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
}

func entRotateInMemory(p *Policy, entry *KeyEntry, rand io.Reader) error {
return fmt.Errorf("unsupported key type %v", p.Type)
}
2 changes: 1 addition & 1 deletion sdk/helper/keysutil/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
var allTestKeyTypes = []KeyType{
KeyType_AES256_GCM96, KeyType_ECDSA_P256, KeyType_ED25519, KeyType_RSA2048,
KeyType_RSA4096, KeyType_ChaCha20_Poly1305, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_AES128_GCM96,
KeyType_RSA3072, KeyType_MANAGED_KEY, KeyType_HMAC, KeyType_AES128_CMAC, KeyType_AES256_CMAC,
KeyType_RSA3072, KeyType_MANAGED_KEY, KeyType_HMAC, KeyType_AES128_CMAC, KeyType_AES256_CMAC, KeyType_ML_DSA,
}

func TestPolicy_KeyTypes(t *testing.T) {
Expand Down

0 comments on commit a0ceaf6

Please sign in to comment.