add ce changes for ecdsa hybrid (#29123)

This commit is contained in:
Rachel Culpepper
2024-12-09 10:58:46 -06:00
committed by GitHub
parent 703897b242
commit 5701c5b492
5 changed files with 273 additions and 134 deletions

View File

@@ -135,6 +135,16 @@ key.`,
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.`,
},
"hybrid_key_type_pqc": {
Type: framework.TypeString,
Description: `The key type of the post-quantum key to use for hybrid signature schemes.
Supported types are: ML-DSA.`,
},
"hybrid_key_type_ec": {
Type: framework.TypeString,
Description: `The key type of the elliptic curve key to use for hybrid signature schemes.
Supported types are: ecdsa-p256, ecdsa-p384, ecdsa-p521.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
@@ -184,6 +194,8 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
managedKeyName := d.Get("managed_key_name").(string)
managedKeyId := d.Get("managed_key_id").(string)
parameterSet := d.Get("parameter_set").(string)
pqcKeyType := d.Get("hybrid_key_type_pqc").(string)
ecKeyType := d.Get("hybrid_key_type_ec").(string)
if autoRotatePeriod != 0 && autoRotatePeriod < time.Hour {
return logical.ErrorResponse("auto rotate period must be 0 to disable or at least an hour"), nil
@@ -241,6 +253,16 @@ func (b *backend) pathPolicyWrite(ctx context.Context, req *logical.Request, d *
return logical.ErrorResponse(fmt.Sprintf("invalid parameter set %s for key type %s", parameterSet, keyType)), logical.ErrInvalidRequest
}
polReq.ParameterSet = parameterSet
case "hybrid":
polReq.KeyType = keysutil.KeyType_HYBRID
var err error
polReq.HybridConfig, err = getHybridKeyConfig(pqcKeyType, parameterSet, ecKeyType)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("invalid config for hybrid key: %s", err)), logical.ErrInvalidRequest
}
polReq.ParameterSet = parameterSet
default:
return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
@@ -393,6 +415,11 @@ func (b *backend) formatKeyPolicy(p *keysutil.Policy, context []byte) (*logical.
resp.Data["parameter_set"] = p.ParameterSet
}
if p.Type == keysutil.KeyType_HYBRID {
resp.Data["hybrid_key_type_pqc"] = p.HybridConfig.PQCKeyType.String()
resp.Data["hybrid_key_type_ec"] = p.HybridConfig.ECKeyType.String()
}
switch p.Type {
case keysutil.KeyType_AES128_GCM96, keysutil.KeyType_AES256_GCM96, keysutil.KeyType_ChaCha20_Poly1305:
retKeys := map[string]int64{}
@@ -401,7 +428,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, keysutil.KeyType_ML_DSA:
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, keysutil.KeyType_HYBRID:
retKeys := map[string]map[string]interface{}{}
for k, v := range p.Keys {
key := asymKey{
@@ -488,6 +515,36 @@ func (b *backend) pathPolicyDelete(ctx context.Context, req *logical.Request, d
return nil, nil
}
func getHybridKeyConfig(pqcKeyType, parameterSet, ecKeyType string) (keysutil.HybridKeyConfig, error) {
config := keysutil.HybridKeyConfig{}
switch pqcKeyType {
case "ml-dsa":
config.PQCKeyType = keysutil.KeyType_ML_DSA
if parameterSet != keysutil.ParameterSet_ML_DSA_44 &&
parameterSet != keysutil.ParameterSet_ML_DSA_65 &&
parameterSet != keysutil.ParameterSet_ML_DSA_87 {
return keysutil.HybridKeyConfig{}, fmt.Errorf("invalid parameter set %s for key type %s", parameterSet, pqcKeyType)
}
default:
return keysutil.HybridKeyConfig{}, fmt.Errorf("invalid PQC key type: %s", pqcKeyType)
}
switch ecKeyType {
case "ecdsa-p256":
config.ECKeyType = keysutil.KeyType_ECDSA_P256
case "ecdsa-p384":
config.ECKeyType = keysutil.KeyType_ECDSA_P384
case "ecdsa-p521":
config.ECKeyType = keysutil.KeyType_ECDSA_P521
default:
return keysutil.HybridKeyConfig{}, fmt.Errorf("invalid key type for hybrid key: %s", ecKeyType)
}
return config, nil
}
const pathPolicyHelpSyn = `Managed named encryption keys`
const pathPolicyHelpDesc = `

View File

@@ -254,6 +254,42 @@ func TestTransit_CreateKey(t *testing.T) {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "87"},
entOnly: true,
},
"Hybrid ML-DSA-44-ECDSA-P256": {
creationParams: map[string]interface{}{"type": "hybrid", "parameter_set": "44", "hybrid_key_type_ec": "ecdsa-p256", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-44-ECDSA-P384": {
creationParams: map[string]interface{}{"type": "hybrid", "parameter_set": "44", "hybrid_key_type_ec": "ecdsa-p384", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-44-ECDSA-P521": {
creationParams: map[string]interface{}{"type": "hybrid", "parameter_set": "44", "hybrid_key_type_ec": "ecdsa-p521", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-65-ECDSA-P256": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "65", "hybrid_key_type_ec": "ecdsa-p256", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-65-ECDSA-P384": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "65", "hybrid_key_type_ec": "ecdsa-p384", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-65-ECDSA-P521": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "65", "hybrid_key_type_ec": "ecdsa-p521", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-87-ECDSA-P256": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "87", "hybrid_key_type_ec": "ecdsa-p256", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-87-ECDSA-P384": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "87", "hybrid_key_type_ec": "ecdsa-p384", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"Hybrid ML-DSA-87-ECDSA-P521": {
creationParams: map[string]interface{}{"type": "ml-dsa", "parameter_set": "87", "hybrid_key_type_ec": "ecdsa-p521", "hybrid_key_type_pqc": "ml-dsa"},
entOnly: true,
},
"bad key type": {
creationParams: map[string]interface{}{"type": "fake-key-type"},
shouldError: true,

View File

@@ -71,6 +71,14 @@ type PolicyRequest struct {
// ParameterSet indicates the parameter set to use with ML-DSA and SLH-DSA keys
ParameterSet string
// HybridConfig contains the key types and parameters for hybrid keys
HybridConfig HybridKeyConfig
}
type HybridKeyConfig struct {
PQCKeyType KeyType
ECKeyType KeyType
}
type LockManager struct {
@@ -412,6 +420,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_HYBRID:
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)
@@ -427,6 +441,7 @@ func (lm *LockManager) GetPolicy(ctx context.Context, req PolicyRequest, rand io
AutoRotatePeriod: req.AutoRotatePeriod,
KeySize: req.KeySize,
ParameterSet: req.ParameterSet,
HybridConfig: req.HybridConfig,
}
if req.Derived {

View File

@@ -73,6 +73,7 @@ const (
KeyType_AES128_CMAC
KeyType_AES256_CMAC
KeyType_ML_DSA
KeyType_HYBRID
// If adding to this list please update allTestKeyTypes in policy_test.go
)
@@ -189,7 +190,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, KeyType_ML_DSA:
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_ED25519, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096, KeyType_MANAGED_KEY, KeyType_ML_DSA, KeyType_HYBRID:
return true
}
return false
@@ -241,7 +242,7 @@ func (kt KeyType) HMACSupported() bool {
func (kt KeyType) IsPQC() bool {
switch kt {
case KeyType_ML_DSA:
case KeyType_ML_DSA, KeyType_HYBRID:
return true
default:
return false
@@ -297,6 +298,8 @@ func (kt KeyType) String() string {
return "aes256-cmac"
case KeyType_ML_DSA:
return "ml-dsa"
case KeyType_HYBRID:
return "hybrid"
}
return "[unknown]"
@@ -570,6 +573,9 @@ type Policy struct {
// ParameterSet indicates the parameter set to use with ML-DSA and SLH-DSA keys
ParameterSet string
// HybridConfig contains the key types and parameters for hybrid keys
HybridConfig HybridKeyConfig
}
func (p *Policy) Lock(exclusive bool) {
@@ -1266,69 +1272,7 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin
switch p.Type {
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
var curveBits int
var curve elliptic.Curve
switch p.Type {
case KeyType_ECDSA_P384:
curveBits = 384
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curveBits = 521
curve = elliptic.P521()
default:
curveBits = 256
curve = elliptic.P256()
}
key := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: keyParams.EC_X,
Y: keyParams.EC_Y,
},
D: keyParams.EC_D,
}
r, s, err := ecdsa.Sign(rand.Reader, key, input)
if err != nil {
return nil, err
}
switch marshaling {
case MarshalingTypeASN1:
// This is used by openssl and X.509
sig, err = asn1.Marshal(ecdsaSignature{
R: r,
S: s,
})
if err != nil {
return nil, err
}
case MarshalingTypeJWS:
// This is used by JWS
// First we have to get the length of the curve in bytes. Although
// we only support 256 now, we'll do this in an agnostic way so we
// can reuse this marshaling if we support e.g. 521. Getting the
// number of bytes without rounding up would be 65.125 so we need
// to add one in that case.
keyLen := curveBits / 8
if curveBits%8 > 0 {
keyLen++
}
// Now create the output array
sig = make([]byte, keyLen*2)
rb := r.Bytes()
sb := s.Bytes()
copy(sig[keyLen-len(rb):], rb)
copy(sig[2*keyLen-len(sb):], sb)
default:
return nil, errutil.UserError{Err: "requested marshaling type is invalid"}
}
sig, err = signWithECDSA(p.Type, keyParams, input, marshaling)
case KeyType_ED25519:
var key ed25519.PrivateKey
@@ -1403,6 +1347,76 @@ func (p *Policy) SignWithOptions(ver int, context, input []byte, options *Signin
return res, nil
}
func signWithECDSA(keyType KeyType, keyParams KeyEntry, input []byte, marshaling MarshalingType) ([]byte, error) {
var curveBits int
var curve elliptic.Curve
switch keyType {
case KeyType_ECDSA_P256:
curveBits = 256
curve = elliptic.P256()
case KeyType_ECDSA_P384:
curveBits = 384
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curveBits = 521
curve = elliptic.P521()
default:
return nil, fmt.Errorf("invalid key type %s for ECDSA", keyType)
}
key := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: keyParams.EC_X,
Y: keyParams.EC_Y,
},
D: keyParams.EC_D,
}
r, s, err := ecdsa.Sign(rand.Reader, key, input)
if err != nil {
return nil, err
}
var sig []byte
switch marshaling {
case MarshalingTypeASN1:
// This is used by openssl and X.509
sig, err = asn1.Marshal(ecdsaSignature{
R: r,
S: s,
})
if err != nil {
return nil, err
}
case MarshalingTypeJWS:
// This is used by JWS
// First we have to get the length of the curve in bytes. Although
// we only support 256 now, we'll do this in an agnostic way so we
// can reuse this marshaling if we support e.g. 521. Getting the
// number of bytes without rounding up would be 65.125 so we need
// to add one in that case.
keyLen := curveBits / 8
if curveBits%8 > 0 {
keyLen++
}
// Now create the output array
sig = make([]byte, keyLen*2)
rb := r.Bytes()
sb := s.Bytes()
copy(sig[keyLen-len(rb):], rb)
copy(sig[2*keyLen-len(sb):], sb)
default:
return nil, errutil.UserError{Err: "requested marshaling type is invalid"}
}
return sig, nil
}
func (p *Policy) VerifySignature(context, input []byte, hashAlgorithm HashType, sigAlgorithm string, marshaling MarshalingType, sig string) (bool, error) {
return p.VerifySignatureWithOptions(context, input, sig, &SigningOptions{
HashAlgorithm: hashAlgorithm,
@@ -1465,49 +1479,11 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o
switch p.Type {
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
var curve elliptic.Curve
switch p.Type {
case KeyType_ECDSA_P384:
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curve = elliptic.P521()
default:
curve = elliptic.P256()
}
var ecdsaSig ecdsaSignature
switch marshaling {
case MarshalingTypeASN1:
rest, err := asn1.Unmarshal(sigBytes, &ecdsaSig)
if err != nil {
return false, errutil.UserError{Err: "supplied signature is invalid"}
}
if rest != nil && len(rest) != 0 {
return false, errutil.UserError{Err: "supplied signature contains extra data"}
}
case MarshalingTypeJWS:
paramLen := len(sigBytes) / 2
rb := sigBytes[:paramLen]
sb := sigBytes[paramLen:]
ecdsaSig.R = new(big.Int)
ecdsaSig.R.SetBytes(rb)
ecdsaSig.S = new(big.Int)
ecdsaSig.S.SetBytes(sb)
}
keyParams, err := p.safeGetKeyEntry(ver)
key, err := p.safeGetKeyEntry(ver)
if err != nil {
return false, err
}
key := &ecdsa.PublicKey{
Curve: curve,
X: keyParams.EC_X,
Y: keyParams.EC_Y,
}
return ecdsa.Verify(key, input, ecdsaSig.R, ecdsaSig.S), nil
return verifyWithECDSA(p.Type, key, input, sigBytes, marshaling)
case KeyType_ED25519:
var pub ed25519.PublicKey
@@ -1586,6 +1562,50 @@ func (p *Policy) VerifySignatureWithOptions(context, input []byte, sig string, o
}
}
func verifyWithECDSA(keyType KeyType, keyParams KeyEntry, input, sigBytes []byte, marshaling MarshalingType) (bool, error) {
var curve elliptic.Curve
switch keyType {
case KeyType_ECDSA_P256:
curve = elliptic.P256()
case KeyType_ECDSA_P384:
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curve = elliptic.P521()
default:
return false, fmt.Errorf("invalid key type %s for ECDSA", keyType)
}
var ecdsaSig ecdsaSignature
switch marshaling {
case MarshalingTypeASN1:
rest, err := asn1.Unmarshal(sigBytes, &ecdsaSig)
if err != nil {
return false, errutil.UserError{Err: "supplied signature is invalid"}
}
if rest != nil && len(rest) != 0 {
return false, errutil.UserError{Err: "supplied signature contains extra data"}
}
case MarshalingTypeJWS:
paramLen := len(sigBytes) / 2
rb := sigBytes[:paramLen]
sb := sigBytes[paramLen:]
ecdsaSig.R = new(big.Int)
ecdsaSig.R.SetBytes(rb)
ecdsaSig.S = new(big.Int)
ecdsaSig.S.SetBytes(sb)
}
key := &ecdsa.PublicKey{
Curve: curve,
X: keyParams.EC_X,
Y: keyParams.EC_Y,
}
return ecdsa.Verify(key, input, ecdsaSig.R, ecdsaSig.S), nil
}
func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte, randReader io.Reader) error {
return p.ImportPublicOrPrivate(ctx, storage, key, true, randReader)
}
@@ -1772,36 +1792,9 @@ func (p *Policy) RotateInMemory(randReader io.Reader) (retErr error) {
}
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
var curve elliptic.Curve
switch p.Type {
case KeyType_ECDSA_P384:
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curve = elliptic.P521()
default:
curve = elliptic.P256()
}
privKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
if err = generateECDSAKey(p.Type, &entry); err != nil {
return err
}
entry.EC_D = privKey.D
entry.EC_X = privKey.X
entry.EC_Y = privKey.Y
derBytes, err := x509.MarshalPKIXPublicKey(privKey.Public())
if err != nil {
return errwrap.Wrapf("error marshaling public key: {{err}}", err)
}
pemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: derBytes,
}
pemBytes := pem.EncodeToMemory(pemBlock)
if pemBytes == nil || len(pemBytes) == 0 {
return fmt.Errorf("error PEM-encoding public key")
}
entry.FormattedPublicKey = string(pemBytes)
case KeyType_ED25519:
// Go uses a 64-byte private key for Ed25519 keys (private+public, each
@@ -2758,3 +2751,40 @@ func (p *Policy) ValidateAndPersistCertificateChain(ctx context.Context, keyVers
p.Keys[strconv.Itoa(keyVersion)] = keyEntry
return p.Persist(ctx, storage)
}
func generateECDSAKey(keyType KeyType, entry *KeyEntry) error {
var curve elliptic.Curve
switch keyType {
case KeyType_ECDSA_P256:
curve = elliptic.P256()
case KeyType_ECDSA_P384:
curve = elliptic.P384()
case KeyType_ECDSA_P521:
curve = elliptic.P521()
default:
return fmt.Errorf("invalid key type %s for ECDSA", keyType)
}
privKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return err
}
entry.EC_D = privKey.D
entry.EC_X = privKey.X
entry.EC_Y = privKey.Y
derBytes, err := x509.MarshalPKIXPublicKey(privKey.Public())
if err != nil {
return errwrap.Wrapf("error marshaling public key: {{err}}", err)
}
pemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: derBytes,
}
pemBytes := pem.EncodeToMemory(pemBlock)
if pemBytes == nil || len(pemBytes) == 0 {
return fmt.Errorf("error PEM-encoding public key")
}
entry.FormattedPublicKey = string(pemBytes)
return nil
}

View File

@@ -36,6 +36,7 @@ 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_ML_DSA,
KeyType_HYBRID,
}
func TestPolicy_KeyTypes(t *testing.T) {