diff --git a/CHANGELOG.md b/CHANGELOG.md index ca792f55..a902e001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased - 0.17.7] - DATE ### Added +- Support for generate extractable keys and certificates on a pkcs#11 module. ### Changed ### Deprecated ### Removed diff --git a/cmd/step-pkcs11-init/main.go b/cmd/step-pkcs11-init/main.go index 8e7bc075..0db1e4d9 100644 --- a/cmd/step-pkcs11-init/main.go +++ b/cmd/step-pkcs11-init/main.go @@ -50,6 +50,7 @@ type Config struct { NoCerts bool EnableSSH bool Force bool + Extractable bool } // Validate checks the flags in the config. @@ -117,6 +118,7 @@ func main() { flag.BoolVar(&c.EnableSSH, "ssh", false, "Enable the creation of ssh keys.") flag.BoolVar(&c.NoCerts, "no-certs", false, "Do not store certificates in the module.") flag.BoolVar(&c.Force, "force", false, "Force the delete of previous keys.") + flag.BoolVar(&c.Extractable, "extractable", false, "Allow export of private keys under wrap.") flag.Usage = usage flag.Parse() @@ -293,6 +295,7 @@ func createPKI(k kms.KeyManager, c Config) error { resp, err := k.CreateKey(&apiv1.CreateKeyRequest{ Name: c.RootKeyObject, SignatureAlgorithm: apiv1.ECDSAWithSHA256, + Extractable: c.Extractable, }) if err != nil { return err @@ -332,6 +335,7 @@ func createPKI(k kms.KeyManager, c Config) error { if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: c.RootObject, Certificate: root, + Extractable: c.Extractable, }); err != nil { return err } @@ -373,6 +377,7 @@ func createPKI(k kms.KeyManager, c Config) error { resp, err := k.CreateKey(&apiv1.CreateKeyRequest{ Name: c.CrtKeyObject, SignatureAlgorithm: apiv1.ECDSAWithSHA256, + Extractable: c.Extractable, }) if err != nil { return err @@ -409,6 +414,7 @@ func createPKI(k kms.KeyManager, c Config) error { if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{ Name: c.CrtObject, Certificate: intermediate, + Extractable: c.Extractable, }); err != nil { return err } diff --git a/kms/apiv1/requests.go b/kms/apiv1/requests.go index f6fe7dd2..762d9dd8 100644 --- a/kms/apiv1/requests.go +++ b/kms/apiv1/requests.go @@ -100,7 +100,7 @@ type GetPublicKeyRequest struct { type CreateKeyRequest struct { // Name represents the key name or label used to identify a key. // - // Used by: awskms, cloudkms, pkcs11, yubikey. + // Used by: awskms, cloudkms, azurekms, pkcs11, yubikey. Name string // SignatureAlgorithm represents the type of key to create. @@ -110,8 +110,14 @@ type CreateKeyRequest struct { Bits int // ProtectionLevel specifies how cryptographic operations are performed. - // Used by: cloudkms + // Used by: cloudkms, azurekms. ProtectionLevel ProtectionLevel + + // Extractable defines if the new key may be exported from the HSM under a + // wrap key. On pkcs11 sets the CKA_EXTRACTABLE bit. + // + // Used by: pkcs11 + Extractable bool } // CreateKeyResponse is the response value of the kms.CreateKey method. @@ -152,4 +158,10 @@ type LoadCertificateRequest struct { type StoreCertificateRequest struct { Name string Certificate *x509.Certificate + + // Extractable defines if the new certificate may be exported from the HSM + // under a wrap key. On pkcs11 sets the CKA_EXTRACTABLE bit. + // + // Used by: pkcs11 + Extractable bool } diff --git a/kms/pkcs11/other_test.go b/kms/pkcs11/other_test.go index 3e168716..9f4ab4a8 100644 --- a/kms/pkcs11/other_test.go +++ b/kms/pkcs11/other_test.go @@ -80,10 +80,23 @@ func (s *stubPKCS11) FindCertificate(id, label []byte, serial *big.Int) (*x509.C } +func (s *stubPKCS11) ImportCertificateWithAttributes(template crypto11.AttributeSet, cert *x509.Certificate) error { + var id, label []byte + if v := template[crypto11.CkaId]; v != nil { + id = v.Value + } + if v := template[crypto11.CkaLabel]; v != nil { + label = v.Value + } + return s.ImportCertificateWithLabel(id, label, cert) +} + func (s *stubPKCS11) ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error { switch { - case id == nil && label == nil: - return errors.New("id and label cannot both be nil") + case id == nil: + return errors.New("id cannot both be nil") + case label == nil: + return errors.New("label cannot both be nil") case cert == nil: return errors.New("certificate cannot be nil") } @@ -111,6 +124,17 @@ func (s *stubPKCS11) DeleteCertificate(id, label []byte, serial *big.Int) error return nil } +func (s *stubPKCS11) GenerateRSAKeyPairWithAttributes(public, private crypto11.AttributeSet, bits int) (crypto11.SignerDecrypter, error) { + var id, label []byte + if v := public[crypto11.CkaId]; v != nil { + id = v.Value + } + if v := public[crypto11.CkaLabel]; v != nil { + label = v.Value + } + return s.GenerateRSAKeyPairWithLabel(id, label, bits) +} + func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) { if id == nil && label == nil { return nil, errors.New("id and label cannot both be nil") @@ -131,6 +155,17 @@ func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (cr return k, nil } +func (s *stubPKCS11) GenerateECDSAKeyPairWithAttributes(public, private crypto11.AttributeSet, curve elliptic.Curve) (crypto11.Signer, error) { + var id, label []byte + if v := public[crypto11.CkaId]; v != nil { + id = v.Value + } + if v := public[crypto11.CkaLabel]; v != nil { + label = v.Value + } + return s.GenerateECDSAKeyPairWithLabel(id, label, curve) +} + func (s *stubPKCS11) GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) { if id == nil && label == nil { return nil, errors.New("id and label cannot both be nil") diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 7924f106..cec05d33 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -32,10 +32,10 @@ const DefaultRSASize = 3072 type P11 interface { FindKeyPair(id, label []byte) (crypto11.Signer, error) FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, error) - ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error + ImportCertificateWithAttributes(template crypto11.AttributeSet, certificate *x509.Certificate) error DeleteCertificate(id, label []byte, serial *big.Int) error - GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) - GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) + GenerateRSAKeyPairWithAttributes(public, private crypto11.AttributeSet, bits int) (crypto11.SignerDecrypter, error) + GenerateECDSAKeyPairWithAttributes(public, private crypto11.AttributeSet, curve elliptic.Curve) (crypto11.Signer, error) Close() error } @@ -185,6 +185,12 @@ func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error { return errors.Wrap(err, "storeCertificate failed") } + // Enforce the use of both id and labels. This is not strictly necessary in + // PKCS #11, but it's a good practice. + if len(id) == 0 || len(object) == 0 { + return errors.Errorf("key with uri %s is not valid, id and object are required", req.Name) + } + cert, err := k.p11.FindCertificate(id, object, nil) if err != nil { return errors.Wrap(err, "storeCertificate failed") @@ -195,7 +201,15 @@ func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error { }, "storeCertificate failed") } - if err := k.p11.ImportCertificateWithLabel(id, object, req.Certificate); err != nil { + // Import certificate with the necessary attributes. + template, err := crypto11.NewAttributeSetWithIDAndLabel(id, object) + if err != nil { + return errors.Wrap(err, "storeCertificate failed") + } + if req.Extractable { + template.Set(crypto11.CkaExtractable, true) + } + if err := k.p11.ImportCertificateWithAttributes(template, req.Certificate); err != nil { return errors.Wrap(err, "storeCertificate failed") } @@ -284,6 +298,16 @@ func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) return nil, errors.Errorf("key with uri %s is not valid, id and object are required", req.Name) } + // Create template for public and private keys + public, err := crypto11.NewAttributeSetWithIDAndLabel(id, object) + if err != nil { + return nil, err + } + private := public.Copy() + if req.Extractable { + private.Set(crypto11.CkaExtractable, true) + } + bits := req.Bits if bits == 0 { bits = DefaultRSASize @@ -291,17 +315,17 @@ func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) switch req.SignatureAlgorithm { case apiv1.UnspecifiedSignAlgorithm: - return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256()) + return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256()) case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: - return ctx.GenerateRSAKeyPairWithLabel(id, object, bits) + return ctx.GenerateRSAKeyPairWithAttributes(public, private, bits) case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: - return ctx.GenerateRSAKeyPairWithLabel(id, object, bits) + return ctx.GenerateRSAKeyPairWithAttributes(public, private, bits) case apiv1.ECDSAWithSHA256: - return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256()) + return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256()) case apiv1.ECDSAWithSHA384: - return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P384()) + return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P384()) case apiv1.ECDSAWithSHA512: - return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P521()) + return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P521()) case apiv1.PureEd25519: return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm) default: diff --git a/kms/pkcs11/pkcs11_test.go b/kms/pkcs11/pkcs11_test.go index 6df9b92a..409cfb3f 100644 --- a/kms/pkcs11/pkcs11_test.go +++ b/kms/pkcs11/pkcs11_test.go @@ -208,6 +208,16 @@ func TestPKCS11_CreateKey(t *testing.T) { SigningKey: testObject, }, }, false}, + {"default extractable", args{&apiv1.CreateKeyRequest{ + Name: testObject, + Extractable: true, + }}, &apiv1.CreateKeyResponse{ + Name: testObject, + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: testObject, + }, + }, false}, {"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSA, @@ -563,6 +573,7 @@ func TestPKCS11_StoreCertificate(t *testing.T) { // Make sure to delete the created certificate t.Cleanup(func() { k.DeleteCertificate(testObject) + k.DeleteCertificate(testObjectAlt) }) type args struct { @@ -577,6 +588,11 @@ func TestPKCS11_StoreCertificate(t *testing.T) { Name: testObject, Certificate: cert, }}, false}, + {"ok extractable", args{&apiv1.StoreCertificateRequest{ + Name: testObjectAlt, + Certificate: cert, + Extractable: true, + }}, false}, {"fail already exists", args{&apiv1.StoreCertificateRequest{ Name: testObject, Certificate: cert, @@ -593,13 +609,22 @@ func TestPKCS11_StoreCertificate(t *testing.T) { Name: "http:id=7770;object=create-cert", Certificate: cert, }}, true}, - {"fail ImportCertificateWithLabel", args{&apiv1.StoreCertificateRequest{ - Name: "pkcs11:foo=bar", + {"fail missing id", args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:object=create-cert", + Certificate: cert, + }}, true}, + {"fail missing object", args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:id=7770;object=", Certificate: cert, }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.args.req.Extractable { + if testModule == "SoftHSM2" { + t.Skip("Extractable certificates are not supported on SoftHSM2") + } + } if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr { t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/kms/pkcs11/setup_test.go b/kms/pkcs11/setup_test.go index 52dc5207..902d89ac 100644 --- a/kms/pkcs11/setup_test.go +++ b/kms/pkcs11/setup_test.go @@ -18,6 +18,7 @@ import ( var ( testModule = "" testObject = "pkcs11:id=7370;object=test-name" + testObjectAlt = "pkcs11:id=7377;object=alt-test-name" testObjectByID = "pkcs11:id=7370" testObjectByLabel = "pkcs11:object=test-name" testKeys = []struct { @@ -105,7 +106,7 @@ func setup(t TBTesting, k *PKCS11) { }); err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{ Message: c.Name + " already exists", }) { - t.Errorf("PKCS1.StoreCertificate() error = %+v", err) + t.Errorf("PKCS1.StoreCertificate() error = %v", err) continue } testCerts[i].Certificates = append(testCerts[i].Certificates, cert)