forked from TrueCloudLab/certificates
Merge pull request #613 from gdbelvin/extractable
Extractable private keys and certs
This commit is contained in:
commit
24a6900de2
7 changed files with 121 additions and 17 deletions
|
@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||||
|
|
||||||
## [Unreleased - 0.17.7] - DATE
|
## [Unreleased - 0.17.7] - DATE
|
||||||
### Added
|
### Added
|
||||||
|
- Support for generate extractable keys and certificates on a pkcs#11 module.
|
||||||
### Changed
|
### Changed
|
||||||
### Deprecated
|
### Deprecated
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
@ -50,6 +50,7 @@ type Config struct {
|
||||||
NoCerts bool
|
NoCerts bool
|
||||||
EnableSSH bool
|
EnableSSH bool
|
||||||
Force bool
|
Force bool
|
||||||
|
Extractable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate checks the flags in the config.
|
// 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.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.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.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.Usage = usage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -293,6 +295,7 @@ func createPKI(k kms.KeyManager, c Config) error {
|
||||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||||
Name: c.RootKeyObject,
|
Name: c.RootKeyObject,
|
||||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
Extractable: c.Extractable,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -332,6 +335,7 @@ func createPKI(k kms.KeyManager, c Config) error {
|
||||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||||
Name: c.RootObject,
|
Name: c.RootObject,
|
||||||
Certificate: root,
|
Certificate: root,
|
||||||
|
Extractable: c.Extractable,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -373,6 +377,7 @@ func createPKI(k kms.KeyManager, c Config) error {
|
||||||
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
resp, err := k.CreateKey(&apiv1.CreateKeyRequest{
|
||||||
Name: c.CrtKeyObject,
|
Name: c.CrtKeyObject,
|
||||||
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
SignatureAlgorithm: apiv1.ECDSAWithSHA256,
|
||||||
|
Extractable: c.Extractable,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -409,6 +414,7 @@ func createPKI(k kms.KeyManager, c Config) error {
|
||||||
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
if err := cm.StoreCertificate(&apiv1.StoreCertificateRequest{
|
||||||
Name: c.CrtObject,
|
Name: c.CrtObject,
|
||||||
Certificate: intermediate,
|
Certificate: intermediate,
|
||||||
|
Extractable: c.Extractable,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ type GetPublicKeyRequest struct {
|
||||||
type CreateKeyRequest struct {
|
type CreateKeyRequest struct {
|
||||||
// Name represents the key name or label used to identify a key.
|
// 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
|
Name string
|
||||||
|
|
||||||
// SignatureAlgorithm represents the type of key to create.
|
// SignatureAlgorithm represents the type of key to create.
|
||||||
|
@ -110,8 +110,14 @@ type CreateKeyRequest struct {
|
||||||
Bits int
|
Bits int
|
||||||
|
|
||||||
// ProtectionLevel specifies how cryptographic operations are performed.
|
// ProtectionLevel specifies how cryptographic operations are performed.
|
||||||
// Used by: cloudkms
|
// Used by: cloudkms, azurekms.
|
||||||
ProtectionLevel ProtectionLevel
|
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.
|
// CreateKeyResponse is the response value of the kms.CreateKey method.
|
||||||
|
@ -152,4 +158,10 @@ type LoadCertificateRequest struct {
|
||||||
type StoreCertificateRequest struct {
|
type StoreCertificateRequest struct {
|
||||||
Name string
|
Name string
|
||||||
Certificate *x509.Certificate
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
func (s *stubPKCS11) ImportCertificateWithLabel(id, label []byte, cert *x509.Certificate) error {
|
||||||
switch {
|
switch {
|
||||||
case id == nil && label == nil:
|
case id == nil:
|
||||||
return errors.New("id and label cannot both be nil")
|
return errors.New("id cannot both be nil")
|
||||||
|
case label == nil:
|
||||||
|
return errors.New("label cannot both be nil")
|
||||||
case cert == nil:
|
case cert == nil:
|
||||||
return errors.New("certificate cannot be 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
|
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) {
|
func (s *stubPKCS11) GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error) {
|
||||||
if id == nil && label == nil {
|
if id == nil && label == nil {
|
||||||
return nil, errors.New("id and label cannot both be 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
|
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) {
|
func (s *stubPKCS11) GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error) {
|
||||||
if id == nil && label == nil {
|
if id == nil && label == nil {
|
||||||
return nil, errors.New("id and label cannot both be nil")
|
return nil, errors.New("id and label cannot both be nil")
|
||||||
|
|
|
@ -32,10 +32,10 @@ const DefaultRSASize = 3072
|
||||||
type P11 interface {
|
type P11 interface {
|
||||||
FindKeyPair(id, label []byte) (crypto11.Signer, error)
|
FindKeyPair(id, label []byte) (crypto11.Signer, error)
|
||||||
FindCertificate(id, label []byte, serial *big.Int) (*x509.Certificate, 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
|
DeleteCertificate(id, label []byte, serial *big.Int) error
|
||||||
GenerateRSAKeyPairWithLabel(id, label []byte, bits int) (crypto11.SignerDecrypter, error)
|
GenerateRSAKeyPairWithAttributes(public, private crypto11.AttributeSet, bits int) (crypto11.SignerDecrypter, error)
|
||||||
GenerateECDSAKeyPairWithLabel(id, label []byte, curve elliptic.Curve) (crypto11.Signer, error)
|
GenerateECDSAKeyPairWithAttributes(public, private crypto11.AttributeSet, curve elliptic.Curve) (crypto11.Signer, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +185,12 @@ func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
|
||||||
return errors.Wrap(err, "storeCertificate failed")
|
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)
|
cert, err := k.p11.FindCertificate(id, object, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "storeCertificate failed")
|
return errors.Wrap(err, "storeCertificate failed")
|
||||||
|
@ -195,7 +201,15 @@ func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error {
|
||||||
}, "storeCertificate failed")
|
}, "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")
|
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)
|
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
|
bits := req.Bits
|
||||||
if bits == 0 {
|
if bits == 0 {
|
||||||
bits = DefaultRSASize
|
bits = DefaultRSASize
|
||||||
|
@ -291,17 +315,17 @@ func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error)
|
||||||
|
|
||||||
switch req.SignatureAlgorithm {
|
switch req.SignatureAlgorithm {
|
||||||
case apiv1.UnspecifiedSignAlgorithm:
|
case apiv1.UnspecifiedSignAlgorithm:
|
||||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256())
|
||||||
case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA:
|
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:
|
case apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS:
|
||||||
return ctx.GenerateRSAKeyPairWithLabel(id, object, bits)
|
return ctx.GenerateRSAKeyPairWithAttributes(public, private, bits)
|
||||||
case apiv1.ECDSAWithSHA256:
|
case apiv1.ECDSAWithSHA256:
|
||||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P256())
|
return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P256())
|
||||||
case apiv1.ECDSAWithSHA384:
|
case apiv1.ECDSAWithSHA384:
|
||||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P384())
|
return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P384())
|
||||||
case apiv1.ECDSAWithSHA512:
|
case apiv1.ECDSAWithSHA512:
|
||||||
return ctx.GenerateECDSAKeyPairWithLabel(id, object, elliptic.P521())
|
return ctx.GenerateECDSAKeyPairWithAttributes(public, private, elliptic.P521())
|
||||||
case apiv1.PureEd25519:
|
case apiv1.PureEd25519:
|
||||||
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
return nil, fmt.Errorf("signature algorithm %s is not supported", req.SignatureAlgorithm)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -208,6 +208,16 @@ func TestPKCS11_CreateKey(t *testing.T) {
|
||||||
SigningKey: testObject,
|
SigningKey: testObject,
|
||||||
},
|
},
|
||||||
}, false},
|
}, 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{
|
{"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{
|
||||||
Name: testObject,
|
Name: testObject,
|
||||||
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
SignatureAlgorithm: apiv1.SHA256WithRSA,
|
||||||
|
@ -563,6 +573,7 @@ func TestPKCS11_StoreCertificate(t *testing.T) {
|
||||||
// Make sure to delete the created certificate
|
// Make sure to delete the created certificate
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
k.DeleteCertificate(testObject)
|
k.DeleteCertificate(testObject)
|
||||||
|
k.DeleteCertificate(testObjectAlt)
|
||||||
})
|
})
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
|
@ -577,6 +588,11 @@ func TestPKCS11_StoreCertificate(t *testing.T) {
|
||||||
Name: testObject,
|
Name: testObject,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
}}, false},
|
}}, false},
|
||||||
|
{"ok extractable", args{&apiv1.StoreCertificateRequest{
|
||||||
|
Name: testObjectAlt,
|
||||||
|
Certificate: cert,
|
||||||
|
Extractable: true,
|
||||||
|
}}, false},
|
||||||
{"fail already exists", args{&apiv1.StoreCertificateRequest{
|
{"fail already exists", args{&apiv1.StoreCertificateRequest{
|
||||||
Name: testObject,
|
Name: testObject,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
|
@ -593,13 +609,22 @@ func TestPKCS11_StoreCertificate(t *testing.T) {
|
||||||
Name: "http:id=7770;object=create-cert",
|
Name: "http:id=7770;object=create-cert",
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
}}, true},
|
}}, true},
|
||||||
{"fail ImportCertificateWithLabel", args{&apiv1.StoreCertificateRequest{
|
{"fail missing id", args{&apiv1.StoreCertificateRequest{
|
||||||
Name: "pkcs11:foo=bar",
|
Name: "pkcs11:object=create-cert",
|
||||||
|
Certificate: cert,
|
||||||
|
}}, true},
|
||||||
|
{"fail missing object", args{&apiv1.StoreCertificateRequest{
|
||||||
|
Name: "pkcs11:id=7770;object=",
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
}}, true},
|
}}, true},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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 {
|
if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr {
|
||||||
t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
var (
|
var (
|
||||||
testModule = ""
|
testModule = ""
|
||||||
testObject = "pkcs11:id=7370;object=test-name"
|
testObject = "pkcs11:id=7370;object=test-name"
|
||||||
|
testObjectAlt = "pkcs11:id=7377;object=alt-test-name"
|
||||||
testObjectByID = "pkcs11:id=7370"
|
testObjectByID = "pkcs11:id=7370"
|
||||||
testObjectByLabel = "pkcs11:object=test-name"
|
testObjectByLabel = "pkcs11:object=test-name"
|
||||||
testKeys = []struct {
|
testKeys = []struct {
|
||||||
|
@ -105,7 +106,7 @@ func setup(t TBTesting, k *PKCS11) {
|
||||||
}); err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
|
}); err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{
|
||||||
Message: c.Name + " already exists",
|
Message: c.Name + " already exists",
|
||||||
}) {
|
}) {
|
||||||
t.Errorf("PKCS1.StoreCertificate() error = %+v", err)
|
t.Errorf("PKCS1.StoreCertificate() error = %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
testCerts[i].Certificates = append(testCerts[i].Certificates, cert)
|
testCerts[i].Certificates = append(testCerts[i].Certificates, cert)
|
||||||
|
|
Loading…
Reference in a new issue