Skip to content

Commit

Permalink
AUT-252: add new public functions to crypto11 that allow us to genera…
Browse files Browse the repository at this point in the history
…te keys with libkmsp11 (#987)

* feat: bump rsa key in signer_test.go to 2048 bits

I'd like to re-use this same key in the soon-to-be-added tests for GCPHSM, which doesn't support 1024 bit keys.

* AUT-252: add new public functions to crypto11 that allow us to generate keys with libkmsp11

libkmsp11 does not accept any attributes on public keys, and very limited attributes on private keys. To accommodate this, I'm adding new public functions to crypto11 that allow us to fully specify the attributes for a new key creation request. The default attributes have been kept for the current functions to allow us to preserve backwards compatible for AWS. This means that we'll end up making different calls in MakeKey depending on whether we're in GCP or AWS, but I think it's preferable to removing the default attributes altogether, which we already know work fine for AWS.

The new functions have been tested in bhearsum/crypto11-test-gcp@1094ddd (technically we could do some testing directly here or in Autograph if we injected the right things to avoid actually calling into libkmsp11...but I haven't been able to convince myself that it's worth spending the time doing it).

A possible alternative here would be to add a separate method in crypto11 that uses the correct values for GCP, but I'm not sure that level of hardcoding is a great idea.

* feat: add GCPHSM implementation
  • Loading branch information
bhearsum authored Sep 27, 2024
1 parent 30d626b commit ae9258e
Show file tree
Hide file tree
Showing 5 changed files with 399 additions and 76 deletions.
94 changes: 60 additions & 34 deletions crypto11/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,49 +225,21 @@ func GenerateECDSAKeyPair(c elliptic.Curve) (*PKCS11PrivateKeyECDSA, error) {
return GenerateECDSAKeyPairOnSlot(instance.slot, nil, nil, c)
}

// GenerateECDSAKeyPairOnSlot creates an ECDSA private key using curve c, on a specified slot.
//
// label and/or id can be nil, in which case random values will be generated.
//
// Only a limited set of named elliptic curves are supported. The
// underlying PKCS#11 implementation may impose further restrictions.
func GenerateECDSAKeyPairOnSlot(slot uint, id []byte, label []byte, c elliptic.Curve) (*PKCS11PrivateKeyECDSA, error) {
var k *PKCS11PrivateKeyECDSA
var err error
if err = ensureSessions(instance, slot); err != nil {
return nil, err
func getDefaultECDSAKeyAttributes(id []byte, label []byte, c elliptic.Curve) ([]*pkcs11.Attribute, []*pkcs11.Attribute, error) {
parameters, err := marshalEcParams(c)
if err != nil {
return nil, nil, err
}
err = withSession(slot, func(session *PKCS11Session) error {
k, err = GenerateECDSAKeyPairOnSession(session, slot, id, label, c)
return err
})
return k, err
}

// GenerateECDSAKeyPairOnSession creates an ECDSA private key using curve c, using a specified session.
//
// label and/or id can be nil, in which case random values will be generated.
//
// Only a limited set of named elliptic curves are supported. The
// underlying PKCS#11 implementation may impose further restrictions.
func GenerateECDSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, label []byte, c elliptic.Curve) (*PKCS11PrivateKeyECDSA, error) {
var err error
var parameters []byte
var pub crypto.PublicKey

if label == nil {
if label, err = generateKeyLabel(); err != nil {
return nil, err
return nil, nil, err
}
}
if id == nil {
if id, err = generateKeyLabel(); err != nil {
return nil, err
return nil, nil, err
}
}
if parameters, err = marshalEcParams(c); err != nil {
return nil, err
}
publicKeyTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
Expand All @@ -285,6 +257,60 @@ func GenerateECDSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte,
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
}

return publicKeyTemplate, privateKeyTemplate, nil
}

// GenerateECDSAKeyPairOnSlot creates an ECDSA private key using curve c, on a specified slot.
//
// label and/or id can be nil, in which case random values will be generated.
//
// Only a limited set of named elliptic curves are supported. The
// underlying PKCS#11 implementation may impose further restrictions.
func GenerateECDSAKeyPairOnSlot(slot uint, id []byte, label []byte, c elliptic.Curve) (*PKCS11PrivateKeyECDSA, error) {
publicKeyTemplate, privateKeyTemplate, err := getDefaultECDSAKeyAttributes(id, label, c)
if err != nil {
return nil, err
}
return GenerateECDSAKeyPairOnSlotWithProvidedAttributes(slot, publicKeyTemplate, privateKeyTemplate)
}

// GenerateECDSAKeyPairOnSession creates an ECDSA private key using curve c, using a specified session.
//
// label and/or id can be nil, in which case random values will be generated.
//
// Only a limited set of named elliptic curves are supported. The
// underlying PKCS#11 implementation may impose further restrictions.
func GenerateECDSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, label []byte, c elliptic.Curve) (*PKCS11PrivateKeyECDSA, error) {
publicKeyTemplate, privateKeyTemplate, err := getDefaultECDSAKeyAttributes(id, label, c)
if err != nil {
return nil, err
}

return GenerateECDSAKeyPairOnSessionWithProvidedAttributes(session, slot, publicKeyTemplate, privateKeyTemplate)
}

// GenerateECDSAKeyPairOnSlotWithProvidedAttributes generates a new ECDSA
// key pair in the given slot with the given public and private attributes
func GenerateECDSAKeyPairOnSlotWithProvidedAttributes(slot uint, publicKeyTemplate []*pkcs11.Attribute, privateKeyTemplate []*pkcs11.Attribute) (*PKCS11PrivateKeyECDSA, error) {
var k *PKCS11PrivateKeyECDSA
var err error
if err = ensureSessions(instance, slot); err != nil {
return nil, err
}
err = withSession(slot, func(session *PKCS11Session) error {
k, err = GenerateECDSAKeyPairOnSessionWithProvidedAttributes(session, slot, publicKeyTemplate, privateKeyTemplate)
return err
})
return k, err
}

// GenerateECDSAKeyPairOnSessionWithProvidedAttributes generates a new ECDSA
// key pair in the given slot with the given public and private attributes
func GenerateECDSAKeyPairOnSessionWithProvidedAttributes(session *PKCS11Session, slot uint, publicKeyTemplate []*pkcs11.Attribute, privateKeyTemplate []*pkcs11.Attribute) (*PKCS11PrivateKeyECDSA, error) {
var err error
var pub crypto.PublicKey

mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA_KEY_PAIR_GEN, nil)}
pubHandle, privHandle, err := session.Ctx.GenerateKeyPair(session.Handle,
mech,
Expand Down
82 changes: 55 additions & 27 deletions crypto11/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,40 +95,16 @@ func GenerateRSAKeyPair(bits int) (*PKCS11PrivateKeyRSA, error) {
return GenerateRSAKeyPairOnSlot(instance.slot, nil, nil, bits)
}

// GenerateRSAKeyPairOnSlot creates a RSA private key on a specified slot
//
// Either or both label and/or id can be nil, in which case random values will be generated.
func GenerateRSAKeyPairOnSlot(slot uint, id []byte, label []byte, bits int) (*PKCS11PrivateKeyRSA, error) {
var k *PKCS11PrivateKeyRSA
var err error
if err = ensureSessions(instance, slot); err != nil {
return nil, err
}
err = withSession(slot, func(session *PKCS11Session) error {
k, err = GenerateRSAKeyPairOnSession(session, slot, id, label, bits)
return err
})
return k, err
}

// GenerateRSAKeyPairOnSession creates an RSA private key of given length, on a specified session.
//
// Either or both label and/or id can be nil, in which case random values will be generated.
//
// RSA private keys are generated with both sign and decrypt
// permissions, and a public exponent of 65537.
func GenerateRSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, label []byte, bits int) (*PKCS11PrivateKeyRSA, error) {
func getDefaultRSAKeyAttributes(id []byte, label []byte, bits int) ([]*pkcs11.Attribute, []*pkcs11.Attribute, error) {
var err error
var pub crypto.PublicKey

if label == nil {
if label, err = generateKeyLabel(); err != nil {
return nil, err
return nil, nil, err
}
}
if id == nil {
if id, err = generateKeyLabel(); err != nil {
return nil, err
return nil, nil, err
}
}
publicKeyTemplate := []*pkcs11.Attribute{
Expand All @@ -151,6 +127,58 @@ func GenerateRSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, l
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
}

return publicKeyTemplate, privateKeyTemplate, nil
}

// GenerateRSAKeyPairOnSlot creates a RSA private key on a specified slot
//
// Either or both label and/or id can be nil, in which case random values will be generated.
func GenerateRSAKeyPairOnSlot(slot uint, id []byte, label []byte, bits int) (*PKCS11PrivateKeyRSA, error) {
publicKeyTemplate, privateKeyTemplate, err := getDefaultRSAKeyAttributes(id, label, bits)
if err != nil {
return nil, err
}

return GenerateRSAKeyPairOnSlotWithProvidedAttributes(slot, publicKeyTemplate, privateKeyTemplate)
}

// GenerateRSAKeyPairOnSession creates an RSA private key of given length, on a specified session.
//
// Either or both label and/or id can be nil, in which case random values will be generated.
//
// RSA private keys are generated with both sign and decrypt
// permissions, and a public exponent of 65537.
func GenerateRSAKeyPairOnSession(session *PKCS11Session, slot uint, id []byte, label []byte, bits int) (*PKCS11PrivateKeyRSA, error) {
publicKeyTemplate, privateKeyTemplate, err := getDefaultRSAKeyAttributes(id, label, bits)
if err != nil {
return nil, err
}

return GenerateRSAKeyPairOnSessionWithProvidedAttributes(session, slot, publicKeyTemplate, privateKeyTemplate)
}

// GenerateRSAKeyPairOnSlotWithProvidedAttributes generates a new RSA
// key pair in the given slot with the given public and private attributes
func GenerateRSAKeyPairOnSlotWithProvidedAttributes(slot uint, publicKeyTemplate []*pkcs11.Attribute, privateKeyTemplate []*pkcs11.Attribute) (*PKCS11PrivateKeyRSA, error) {
var k *PKCS11PrivateKeyRSA
var err error
if err = ensureSessions(instance, slot); err != nil {
return nil, err
}
err = withSession(slot, func(session *PKCS11Session) error {
k, err = GenerateRSAKeyPairOnSessionWithProvidedAttributes(session, slot, publicKeyTemplate, privateKeyTemplate)
return err
})
return k, err
}

// GenerateRSAKeyPairOnSessionWithProvidedAttributes generates a new RSA
// key pair in the given slot with the given public and private attributes
func GenerateRSAKeyPairOnSessionWithProvidedAttributes(session *PKCS11Session, slot uint, publicKeyTemplate []*pkcs11.Attribute, privateKeyTemplate []*pkcs11.Attribute) (*PKCS11PrivateKeyRSA, error) {
var err error
var pub crypto.PublicKey

mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil)}
pubHandle, privHandle, err := session.Ctx.GenerateKeyPair(session.Handle,
mech,
Expand Down
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,12 @@ func (a *autographer) initHSM(conf configuration) error {
return fmt.Errorf("error in initHSM from crypto11.Configure: %w", err)
}
if tmpCtx != nil {
var hsm signer.HSM
if os.Getenv("KMS_PKCS11_CONFIG") != "" {
hsm = signer.NewGCPHSM(tmpCtx)
} else {
hsm = signer.NewAWSHSM(tmpCtx)
}
// if we successfully initialized the crypto11 context,
// tell the signers they can try using the HSM
for i := range conf.Signers {
Expand All @@ -402,7 +408,7 @@ func (a *autographer) initHSM(conf configuration) error {
// TODO(AUT-203): when we make `signer.Configuration` immutable,
// we'll not need this strange `conf.Signers[i]` and can loop
// through them normally.
conf.Signers[i].InitHSM(signer.NewAWSHSM(tmpCtx))
conf.Signers[i].InitHSM(hsm)
signerConf := &conf.Signers[i]

if signerConf.PrivateKeyHasPEMPrefix() {
Expand Down
100 changes: 99 additions & 1 deletion signer/hsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import (
"fmt"
"io"

"github.com/miekg/pkcs11"
"github.com/mozilla-services/autograph/crypto11"
)

type HSM interface {
GetPrivateKey(label []byte) (crypto.PrivateKey, error)
// MakeKey generates a new keypair of type `keyTpl` and returns the new key structs.
MakeKey(keyTpl interface{}, keyName string) (crypto.PrivateKey, crypto.PublicKey, error)
GetRand() io.Reader
}
Expand All @@ -33,7 +35,6 @@ type AWSHSM struct {
GenericHSM
}

// MakeKey generates a new keypair of type `keyTpl` and returns the new key structs.
func (hsm *AWSHSM) MakeKey(keyTpl interface{}, keyName string) (crypto.PrivateKey, crypto.PublicKey, error) {
var slots []uint
slots, err := hsm.ctx.GetSlotList(true)
Expand Down Expand Up @@ -72,3 +73,100 @@ func NewAWSHSM(ctx crypto11.PKCS11Context) *AWSHSM {
},
}
}

// Constants from https://github.com/GoogleCloudPlatform/kms-integrations/blob/master/kmsp11/kmsp11.h
// that are needed when generating ECDSA or RSA keys in GCP KMS.

// A marker for a PKCS #11 attribute or flag defined by Google.
// (Note that 0x80000000UL is CKA_VENDOR_DEFINED).
const CKA_GOOGLE_DEFINED = 0x80000000 | 0x1E100

// An attribute that indicates the backing CryptoKeyVersionAlgorithm in Cloud
// KMS.
const CKA_KMS_ALGORITHM = CKA_GOOGLE_DEFINED | 0x01

// ECDSA on the NIST P-256 curve with a SHA256 digest.
const KMS_ALGORITHM_EC_SIGN_P256_SHA256 = 12

// ECDSA on the NIST P-384 curve with a SHA384 digest.
const KMS_ALGORITHM_EC_SIGN_P384_SHA384 = 13

// RSASSA-PKCS1-v1_5 with a 2048 bit key and a SHA256 digest.
const KMS_ALGORITHM_RSA_SIGN_PKCS1_2048_SHA256 = 5

// RSASSA-PKCS1-v1_5 with a 3072 bit key and a SHA256 digest.
const KMS_ALGORITHM_RSA_SIGN_PKCS1_3072_SHA256 = 6

// RSASSA-PKCS1-v1_5 with a 4096 bit key and a SHA256 digest.
const KMS_ALGORITHM_RSA_SIGN_PKCS1_4096_SHA256 = 7

// Our own constant; simply a shortcut for a combination we use in a few places
const CKA_GOOGLE_DEFINED_KMS_ALGORITHM = CKA_GOOGLE_DEFINED | CKA_KMS_ALGORITHM

type GCPHSM struct {
GenericHSM
}

func (hsm *GCPHSM) MakeKey(keyTpl interface{}, keyName string) (crypto.PrivateKey, crypto.PublicKey, error) {
var slots []uint
slots, err := hsm.ctx.GetSlotList(true)
if err != nil {
return nil, nil, fmt.Errorf("failed to list PKCS#11 Slots: %w", err)
}
if len(slots) < 1 {
return nil, nil, fmt.Errorf("failed to find a usable slot in hsm context")
}
publicKeyTemplate := []*pkcs11.Attribute{}
privateKeyTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(keyName)),
}
switch keyTplType := keyTpl.(type) {
case *ecdsa.PublicKey:
size := keyTplType.Params().BitSize
switch size {
case 256:
privateKeyTemplate = append(privateKeyTemplate, pkcs11.NewAttribute(CKA_GOOGLE_DEFINED_KMS_ALGORITHM, KMS_ALGORITHM_EC_SIGN_P256_SHA256))
case 384:
privateKeyTemplate = append(privateKeyTemplate, pkcs11.NewAttribute(CKA_GOOGLE_DEFINED_KMS_ALGORITHM, KMS_ALGORITHM_EC_SIGN_P384_SHA384))
default:
return nil, nil, fmt.Errorf("invalid elliptic curve: must be p256 or p384")
}

priv, err := crypto11.GenerateECDSAKeyPairOnSlotWithProvidedAttributes(slots[0], publicKeyTemplate, privateKeyTemplate)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate ecdsa key in hsm: %w", err)
}
pub := priv.PubKey.(*ecdsa.PublicKey)
return priv, pub, nil

case *rsa.PublicKey:
keySizeBytes := keyTplType.Size()
switch keySizeBytes {
case 256:
privateKeyTemplate = append(privateKeyTemplate, pkcs11.NewAttribute(CKA_GOOGLE_DEFINED_KMS_ALGORITHM, KMS_ALGORITHM_RSA_SIGN_PKCS1_2048_SHA256))
case 384:
privateKeyTemplate = append(privateKeyTemplate, pkcs11.NewAttribute(CKA_GOOGLE_DEFINED_KMS_ALGORITHM, KMS_ALGORITHM_RSA_SIGN_PKCS1_3072_SHA256))
case 512:
privateKeyTemplate = append(privateKeyTemplate, pkcs11.NewAttribute(CKA_GOOGLE_DEFINED_KMS_ALGORITHM, KMS_ALGORITHM_RSA_SIGN_PKCS1_4096_SHA256))
default:
return nil, nil, fmt.Errorf("invalid rsa key size: got: %d", keySizeBytes)
}

priv, err := crypto11.GenerateRSAKeyPairOnSlotWithProvidedAttributes(slots[0], publicKeyTemplate, privateKeyTemplate)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate rsa key in hsm: %w", err)
}
pub := priv.PubKey.(*rsa.PublicKey)
return priv, pub, nil
}

return nil, nil, fmt.Errorf("making key of type %T is not supported", keyTpl)
}

func NewGCPHSM(ctx crypto11.PKCS11Context) *GCPHSM {
return &GCPHSM{
GenericHSM{
ctx,
},
}
}
Loading

0 comments on commit ae9258e

Please sign in to comment.