diff --git a/kms/apiv1/options.go b/kms/apiv1/options.go index 07d7df0d..705f3633 100644 --- a/kms/apiv1/options.go +++ b/kms/apiv1/options.go @@ -26,16 +26,29 @@ type CertificateManager interface { // ErrNotImplemented is the type of error returned if an operation is not // implemented. type ErrNotImplemented struct { - msg string + Message string } func (e ErrNotImplemented) Error() string { - if e.msg != "" { - return e.msg + if e.Message != "" { + return e.Message } return "not implemented" } +// ErrAlreadyExists is the type of error returned if a key already exists. This +// is currently only implmented on pkcs11. +type ErrAlreadyExists struct { + Message string +} + +func (e ErrAlreadyExists) Error() string { + if e.Message != "" { + return e.Message + } + return "key already exists" +} + // Type represents the KMS type used. type Type string diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index d0a429f8..d45f8045 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -21,9 +21,25 @@ import ( // specified. const DefaultRSASize = 3072 +// P11 defines the methods on crypto11.Context that this package will use. This +// interface will be used for unit testing. +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 + 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) + Close() error +} + +var p11Configure = func(config *crypto11.Config) (P11, error) { + return crypto11.Configure(config) +} + // PKCS11 is the implementation of a KMS using the PKCS #11 standard. type PKCS11 struct { - context *crypto11.Context + p11 P11 } // New returns a new PKCS11 KMS. @@ -54,13 +70,13 @@ func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) { return nil, errors.New("kms uri 'token' or 'serial' are mutually exclusive") } - p11Ctx, err := crypto11.Configure(&config) + p11, err := p11Configure(&config) if err != nil { return nil, errors.Wrap(err, "error initializing PKCS#11") } return &PKCS11{ - context: p11Ctx, + p11: p11, }, nil } @@ -76,7 +92,7 @@ func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") } - signer, err := findSigner(k.context, req.Name) + signer, err := findSigner(k.p11, req.Name) if err != nil { return nil, errors.Wrap(err, "getPublicKey failed") } @@ -93,7 +109,7 @@ func (k *PKCS11) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons return nil, errors.New("createKeyRequest 'bits' cannot be negative") } - signer, err := generateKey(k.context, req) + signer, err := generateKey(k.p11, req) if err != nil { return nil, errors.Wrap(err, "createKey failed") } @@ -115,7 +131,7 @@ func (k *PKCS11) CreateSigner(req *apiv1.CreateSignerRequest) (crypto.Signer, er return nil, errors.New("createSignerRequest 'signingKey' cannot be empty") } - signer, err := findSigner(k.context, req.SigningKey) + signer, err := findSigner(k.p11, req.SigningKey) if err != nil { return nil, errors.Wrap(err, "createSigner failed") } @@ -129,7 +145,7 @@ func (k *PKCS11) LoadCertificate(req *apiv1.LoadCertificateRequest) (*x509.Certi if req.Name == "" { return nil, errors.New("loadCertificateRequest 'name' cannot be nil") } - cert, err := findCertificate(k.context, req.Name) + cert, err := findCertificate(k.p11, req.Name) if err != nil { return nil, errors.Wrap(err, "loadCertificate failed") } @@ -151,7 +167,7 @@ func (k *PKCS11) StoreCertificate(req *apiv1.StoreCertificateRequest) error { return errors.Wrap(err, "storeCertificate failed") } - if err := k.context.ImportCertificateWithLabel(id, object, req.Certificate); err != nil { + if err := k.p11.ImportCertificateWithLabel(id, object, req.Certificate); err != nil { return errors.Wrap(err, "storeCertificate failed") } @@ -164,7 +180,7 @@ func (k *PKCS11) DeleteKey(uri string) error { if err != nil { return errors.Wrap(err, "deleteKey failed") } - signer, err := k.context.FindKeyPair(id, object) + signer, err := k.p11.FindKeyPair(id, object) if err != nil { return errors.Wrap(err, "deleteKey failed") } @@ -183,7 +199,7 @@ func (k *PKCS11) DeleteCertificate(uri string) error { if err != nil { return errors.Wrap(err, "deleteCertificate failed") } - if err := k.context.DeleteCertificate(id, object, nil); err != nil { + if err := k.p11.DeleteCertificate(id, object, nil); err != nil { return errors.Wrap(err, "deleteCertificate failed") } return nil @@ -191,7 +207,7 @@ func (k *PKCS11) DeleteCertificate(uri string) error { // Close releases the connection to the PKCS#11 module. func (k *PKCS11) Close() error { - return errors.Wrap(k.context.Close(), "error closing pkcs#11 context") + return errors.Wrap(k.p11.Close(), "error closing pkcs#11 context") } func toByte(s string) []byte { @@ -201,7 +217,24 @@ func toByte(s string) []byte { return []byte(s) } -func generateKey(ctx *crypto11.Context, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) { +func parseObject(rawuri string) ([]byte, []byte, error) { + u, err := uri.ParseWithScheme("pkcs11", rawuri) + if err != nil { + return nil, nil, err + } + id, err := u.GetHex("id") + if err != nil { + return nil, nil, err + } + object := u.Get("object") + if len(id) == 0 && object == "" { + return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri) + } + + return id, toByte(object), nil +} + +func generateKey(ctx P11, req *apiv1.CreateKeyRequest) (crypto11.Signer, error) { id, object, err := parseObject(req.Name) if err != nil { return nil, err @@ -211,7 +244,9 @@ func generateKey(ctx *crypto11.Context, req *apiv1.CreateKeyRequest) (crypto11.S return nil, err } if signer != nil { - return nil, errors.Errorf("%s already exists", req.Name) + return nil, apiv1.ErrAlreadyExists{ + Message: req.Name + " already exists", + } } bits := req.Bits @@ -239,24 +274,7 @@ func generateKey(ctx *crypto11.Context, req *apiv1.CreateKeyRequest) (crypto11.S } } -func parseObject(rawuri string) ([]byte, []byte, error) { - u, err := uri.ParseWithScheme("pkcs11", rawuri) - if err != nil { - return nil, nil, err - } - id, err := u.GetHex("id") - if err != nil { - return nil, nil, err - } - object := u.Get("object") - if len(id) == 0 && object == "" { - return nil, nil, errors.Errorf("key with uri %s is not valid, id or object are required", rawuri) - } - - return id, toByte(object), nil -} - -func findSigner(ctx *crypto11.Context, rawuri string) (crypto11.Signer, error) { +func findSigner(ctx P11, rawuri string) (crypto11.Signer, error) { id, object, err := parseObject(rawuri) if err != nil { return nil, err @@ -271,7 +289,7 @@ func findSigner(ctx *crypto11.Context, rawuri string) (crypto11.Signer, error) { return signer, nil } -func findCertificate(ctx *crypto11.Context, rawuri string) (*x509.Certificate, error) { +func findCertificate(ctx P11, rawuri string) (*x509.Certificate, error) { u, err := uri.ParseWithScheme("pkcs11", rawuri) if err != nil { return nil, err diff --git a/kms/pkcs11/pkcs11_test.go b/kms/pkcs11/pkcs11_test.go index 835fd47d..f0e8bafc 100644 --- a/kms/pkcs11/pkcs11_test.go +++ b/kms/pkcs11/pkcs11_test.go @@ -1,3 +1,644 @@ // +build cgo package pkcs11 + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "reflect" + "testing" + + "github.com/smallstep/certificates/kms/apiv1" +) + +func TestNew(t *testing.T) { + type args struct { + ctx context.Context + opts apiv1.Options + } + tests := []struct { + name string + args args + want *PKCS11 + wantErr bool + }{} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := New(tt.args.ctx, tt.args.opts) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPKCS11_GetPublicKey(t *testing.T) { + setupSoftHSM2, setupYubiHSM2 := setupFuncs(t) + type args struct { + req *apiv1.GetPublicKeyRequest + } + tests := []struct { + name string + setup func(t *testing.T) *PKCS11 + args args + want crypto.PublicKey + wantErr bool + }{ + // SoftHSM2 + {"softhsm RSA", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7371;object=rsa-key", + }}, &rsa.PublicKey{}, false}, + {"softhsm RSA by id", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7371", + }}, &rsa.PublicKey{}, false}, + {"softhsm RSA by label", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:object=rsa-key", + }}, &rsa.PublicKey{}, false}, + {"softhsm ECDSA", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7373;object=ecdsa-p256-key", + }}, &ecdsa.PublicKey{}, false}, + {"softhsm ECDSA by id", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7373", + }}, &ecdsa.PublicKey{}, false}, + {"softhsm ECDSA by label", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:object=ecdsa-p256-key", + }}, &ecdsa.PublicKey{}, false}, + // YubiHSM2 + {"yubiHSM2 RSA", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7371;object=rsa-key", + }}, &rsa.PublicKey{}, false}, + {"yubiHSM2 RSA by id", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7371", + }}, &rsa.PublicKey{}, false}, + {"yubiHSM2 RSA by label", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:object=rsa-key", + }}, &rsa.PublicKey{}, false}, + {"yubiHSM2 ECDSA", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7373;object=ecdsa-p256-key", + }}, &ecdsa.PublicKey{}, false}, + {"yubiHSM2 ECDSA by id", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=7373", + }}, &ecdsa.PublicKey{}, false}, + {"yubiHSM2 ECDSA by label", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:object=ecdsa-p256-key", + }}, &ecdsa.PublicKey{}, false}, + // Errors + {"fail name", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "", + }}, nil, true}, + {"fail uri", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "https:id=9999;object=https", + }}, nil, true}, + {"fail softhsm missing", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=9999;object=rsa-key", + }}, nil, true}, + {"fail yubiHSM2 missing", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:id=9999;object=ecdsa-p256-key", + }}, nil, true}, + {"fail softhsm FindKeyPair", setupSoftHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:foo=bar", + }}, nil, true}, + {"fail yubiHSM2 FindKeyPair", setupYubiHSM2, args{&apiv1.GetPublicKeyRequest{ + Name: "pkcs11:foo=bar", + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.setup(t) + got, err := k.GetPublicKey(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("PKCS11.GetPublicKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if reflect.TypeOf(got) != reflect.TypeOf(tt.want) { + t.Errorf("PKCS11.GetPublicKey() = %T, want %T", got, tt.want) + } + }) + } +} + +func TestPKCS11_CreateKey(t *testing.T) { + setupSoftHSM2, setupYubiHSM2 := setupFuncs(t) + type args struct { + req *apiv1.CreateKeyRequest + } + tests := []struct { + name string + setup func(t *testing.T) *PKCS11 + args args + want *apiv1.CreateKeyResponse + wantErr bool + }{ + // SoftHSM2 + {"softhsm Default", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=ecdsa-create-key", + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=ecdsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=ecdsa-create-key", + }, + }, false}, + {"softhsm RSA SHA256WithRSA", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA SHA384WithRSA", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA384WithRSA, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA SHA512WithRSA", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA512WithRSA, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA SHA256WithRSAPSS", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSAPSS, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA SHA384WithRSAPSS", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA384WithRSAPSS, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA SHA512WithRSAPSS", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA512WithRSAPSS, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA 2048", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + Bits: 2048, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm RSA 4096", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + Bits: 4096, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm ECDSA P256", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA256, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm ECDSA P384", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA384, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"softhsm ECDSA P521", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA512, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + // YubiHSM2 + {"yubihsm RSA", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"yubihsm RSA 2048", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + Bits: 2048, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"yubihsm RSA 4096", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.SHA256WithRSA, + Bits: 4096, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &rsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"yubihsm Default", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=ecdsa-create-key", + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=ecdsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=ecdsa-create-key", + }, + }, false}, + {"yubihsm ECDSA P256", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA256, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"yubihsm ECDSA P384", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA384, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + {"yubihsm ECDSA P521", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=7771;object=rsa-create-key", + SignatureAlgorithm: apiv1.ECDSAWithSHA512, + }}, &apiv1.CreateKeyResponse{ + Name: "pkcs11:id=7771;object=rsa-create-key", + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7771;object=rsa-create-key", + }, + }, false}, + // Errors + {"fail name", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "", + }}, nil, true}, + {"fail bits", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=9999;object=rsa-create-key", + Bits: -1, + SignatureAlgorithm: apiv1.SHA256WithRSAPSS, + }}, nil, true}, + {"fail ed25519", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=9999;object=rsa-create-key", + SignatureAlgorithm: apiv1.PureEd25519, + }}, nil, true}, + {"fail unknown", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=9999;object=rsa-create-key", + SignatureAlgorithm: apiv1.SignatureAlgorithm(100), + }}, nil, true}, + {"fail uri", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:id=xxxx;object=https", + SignatureAlgorithm: apiv1.SHA256WithRSAPSS, + }}, nil, true}, + {"fail softhsm FindKeyPair", setupSoftHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:foo=bar", + SignatureAlgorithm: apiv1.SHA256WithRSAPSS, + }}, nil, true}, + {"fail yubihsm FindKeyPair", setupYubiHSM2, args{&apiv1.CreateKeyRequest{ + Name: "pkcs11:foo=bar", + SignatureAlgorithm: apiv1.SHA256WithRSAPSS, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.setup(t) + got, err := k.CreateKey(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("PKCS11.CreateKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil { + got.PublicKey = tt.want.PublicKey + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("PKCS11.CreateKey() = %v, want %v", got, tt.want) + } + if got != nil { + if err := k.DeleteKey(got.Name); err != nil { + t.Errorf("PKCS11.DeleteKey() error = %v", err) + } + } + }) + } +} + +func TestPKCS11_CreateSigner(t *testing.T) { + data := []byte("buggy-coheir-RUBRIC-rabbet-liberal-eaglet-khartoum-stagger") + setupSoftHSM2, setupYubiHSM2 := setupFuncs(t) + + type args struct { + req *apiv1.CreateSignerRequest + } + tests := []struct { + name string + setup func(t *testing.T) *PKCS11 + args args + algorithm apiv1.SignatureAlgorithm + signerOpts crypto.SignerOpts + wantErr bool + }{ + // SoftHSM2 + {"softhsm RSA", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7371;object=rsa-key", + }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, + {"softhsm RSA PSS", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7371;object=rsa-key", + }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, + {"softhsm ECDSA P256", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key", + }}, apiv1.ECDSAWithSHA256, crypto.SHA256, false}, + {"softhsm ECDSA P384", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key", + }}, apiv1.ECDSAWithSHA384, crypto.SHA384, false}, + {"softhsm ECDSA P521", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key", + }}, apiv1.ECDSAWithSHA512, crypto.SHA512, false}, + // YubiHSM2 + {"yubihsm RSA", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7371;object=rsa-key", + }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, + {"yubihsm RSA PSS", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7371;object=rsa-key", + }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, + {"yubihsm ECDSA P256", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key", + }}, apiv1.ECDSAWithSHA256, crypto.SHA256, false}, + {"yubihsm ECDSA P384", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key", + }}, apiv1.ECDSAWithSHA384, crypto.SHA384, false}, + {"yubihsm ECDSA P521", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key", + }}, apiv1.ECDSAWithSHA512, crypto.SHA512, false}, + // Errors + {"fail SigningKey", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "", + }}, 0, nil, true}, + {"fail uri", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "https:id=7375;object=ecdsa-p521-key", + }}, 0, nil, true}, + {"fail softhsm FindKeyPair", setupSoftHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:foo=bar", + }}, 0, nil, true}, + {"fail yubihsm FindKeyPair", setupYubiHSM2, args{&apiv1.CreateSignerRequest{ + SigningKey: "pkcs11:foo=bar", + }}, 0, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.setup(t) + got, err := k.CreateSigner(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("PKCS11.CreateSigner() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if got != nil { + hash := tt.signerOpts.HashFunc() + h := hash.New() + h.Write(data) + digest := h.Sum(nil) + sig, err := got.Sign(rand.Reader, digest, tt.signerOpts) + if err != nil { + t.Errorf("cyrpto.Signer.Sign() error = %v", err) + } + + switch tt.algorithm { + case apiv1.SHA256WithRSA, apiv1.SHA384WithRSA, apiv1.SHA512WithRSA: + pub := got.Public().(*rsa.PublicKey) + if err := rsa.VerifyPKCS1v15(pub, hash, digest, sig); err != nil { + t.Errorf("rsa.VerifyPKCS1v15() error = %v", err) + } + case apiv1.UnspecifiedSignAlgorithm, apiv1.SHA256WithRSAPSS, apiv1.SHA384WithRSAPSS, apiv1.SHA512WithRSAPSS: + pub := got.Public().(*rsa.PublicKey) + if err := rsa.VerifyPSS(pub, hash, digest, sig, tt.signerOpts.(*rsa.PSSOptions)); err != nil { + t.Errorf("rsa.VerifyPSS() error = %v", err) + } + case apiv1.ECDSAWithSHA256, apiv1.ECDSAWithSHA384, apiv1.ECDSAWithSHA512: + pub := got.Public().(*ecdsa.PublicKey) + if !ecdsa.VerifyASN1(pub, digest, sig) { + t.Error("ecdsa.VerifyASN1() failed") + } + default: + t.Errorf("signature algorithm %s is not supported", tt.algorithm) + } + + } + + }) + } +} + +func TestPKCS11_LoadCertificate(t *testing.T) { + setupSoftHSM2, setupYubiHSM2 := setupFuncs(t) + + getCertFn := func(i, j int) func() *x509.Certificate { + return func() *x509.Certificate { + return testCerts[i].Certificates[j] + } + } + + type args struct { + req *apiv1.LoadCertificateRequest + } + tests := []struct { + name string + setup func(t *testing.T) *PKCS11 + args args + wantFn func() *x509.Certificate + wantErr bool + }{ + {"softhsm", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=7370;object=root", + }}, getCertFn(0, 0), false}, + {"softhsm by id", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=7370", + }}, getCertFn(0, 0), false}, + {"softhsm by label", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:object=root", + }}, getCertFn(0, 0), false}, + {"yubihsm", setupYubiHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=7370;object=root", + }}, getCertFn(0, 1), false}, + {"yubihsm by id", setupYubiHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=7370", + }}, getCertFn(0, 1), false}, + {"yubihsm by label", setupYubiHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:object=root", + }}, getCertFn(0, 1), false}, + {"fail softhsm missing", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=9999;object=root", + }}, nil, true}, + {"fail yubihsm missing", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=9999;object=root", + }}, nil, true}, + {"fail name", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "", + }}, nil, true}, + {"fail uri", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:id=xxxx;object=root", + }}, nil, true}, + {"fail softhsm FindCertificate", setupSoftHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:foo=bar", + }}, nil, true}, + {"fail yubihsm FindCertificate", setupYubiHSM2, args{&apiv1.LoadCertificateRequest{ + Name: "pkcs11:foo=bar", + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.setup(t) + got, err := k.LoadCertificate(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("PKCS11.LoadCertificate() error = %v, wantErr %v", err, tt.wantErr) + return + } + var want *x509.Certificate + if tt.wantFn != nil { + want = tt.wantFn() + got.Raw, got.RawIssuer, got.RawSubject, got.RawTBSCertificate, got.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil + want.Raw, want.RawIssuer, want.RawSubject, want.RawTBSCertificate, want.RawSubjectPublicKeyInfo = nil, nil, nil, nil, nil + } + if !reflect.DeepEqual(got, want) { + t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, want) + } + }) + } +} + +func TestPKCS11_StoreCertificate(t *testing.T) { + setupSoftHSM2, setupYubiHSM2 := setupFuncs(t) + + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("ed25519.GenerateKey() error = %v", err) + } + + cert, err := generateCertificate(pub, priv) + if err != nil { + t.Fatalf("x509.CreateCertificate() error = %v", err) + } + + type args struct { + req *apiv1.StoreCertificateRequest + } + tests := []struct { + name string + setup func(t *testing.T) *PKCS11 + args args + wantErr bool + }{ + {"softhsm", setupSoftHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:id=7771;object=root", + Certificate: cert, + }}, false}, + {"yubihsm", setupYubiHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:id=7771;object=root", + Certificate: cert, + }}, false}, + {"fail name", setupSoftHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "", + Certificate: cert, + }}, true}, + {"fail certificate", setupSoftHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:id=7771;object=root", + Certificate: nil, + }}, true}, + {"fail uri", setupSoftHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "http:id=7771;object=root", + Certificate: cert, + }}, true}, + {"fail softhsm ImportCertificateWithLabel", setupSoftHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:foo=bar", + Certificate: cert, + }}, true}, + {"fail yubihsm ImportCertificateWithLabel", setupYubiHSM2, args{&apiv1.StoreCertificateRequest{ + Name: "pkcs11:foo=bar", + Certificate: cert, + }}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := tt.setup(t) + if err := k.StoreCertificate(tt.args.req); (err != nil) != tt.wantErr { + t.Errorf("PKCS11.StoreCertificate() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr { + got, err := k.LoadCertificate(&apiv1.LoadCertificateRequest{ + Name: tt.args.req.Name, + }) + if err != nil { + t.Errorf("PKCS11.LoadCertificate() error = %v", err) + } + if !reflect.DeepEqual(got, cert) { + t.Errorf("PKCS11.LoadCertificate() = %v, want %v", got, cert) + } + if err := k.DeleteCertificate(tt.args.req.Name); err != nil { + t.Errorf("PKCS11.DeleteCertificate() error = %v", err) + } + } + }) + } +} diff --git a/kms/pkcs11/setup_test.go b/kms/pkcs11/setup_test.go new file mode 100644 index 00000000..47c6ab3b --- /dev/null +++ b/kms/pkcs11/setup_test.go @@ -0,0 +1,235 @@ +// +build cgo + +package pkcs11 + +import ( + "crypto" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "runtime" + "sync" + "testing" + "time" + + "github.com/pkg/errors" + + "github.com/ThalesIgnite/crypto11" + "github.com/smallstep/certificates/kms/apiv1" +) + +var ( + softHSM2Once sync.Once + yubiHSM2Once sync.Once +) + +var ( + testKeys = []struct { + Name string + SignatureAlgorithm apiv1.SignatureAlgorithm + Bits int + }{ + {"pkcs11:id=7371;object=rsa-key", apiv1.SHA256WithRSA, 2048}, + {"pkcs11:id=7372;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize}, + {"pkcs11:id=7373;object=ecdsa-p256-key", apiv1.ECDSAWithSHA256, 0}, + {"pkcs11:id=7374;object=ecdsa-p384-key", apiv1.ECDSAWithSHA384, 0}, + {"pkcs11:id=7375;object=ecdsa-p521-key", apiv1.ECDSAWithSHA512, 0}, + } + + testCerts = []struct { + Name string + Key string + Certificates []*x509.Certificate + }{ + {"pkcs11:id=7370;object=root", "pkcs11:id=7373;object=ecdsa-p256-key", nil}, + } +) + +func generateCertificate(pub crypto.PublicKey, signer crypto.Signer) (*x509.Certificate, error) { + now := time.Now() + template := &x509.Certificate{ + Subject: pkix.Name{CommonName: "Test Root Certificate"}, + Issuer: pkix.Name{CommonName: "Test Root Certificate"}, + IsCA: true, + MaxPathLen: 1, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + NotBefore: now, + NotAfter: now.Add(time.Hour), + SerialNumber: big.NewInt(100), + } + + b, err := x509.CreateCertificate(rand.Reader, template, template, pub, signer) + if err != nil { + return nil, err + } + + return x509.ParseCertificate(b) +} + +func setup(t *testing.T, k *PKCS11) { + for _, tk := range testKeys { + _, err := k.CreateKey(&apiv1.CreateKeyRequest{ + Name: tk.Name, + SignatureAlgorithm: tk.SignatureAlgorithm, + Bits: tk.Bits, + }) + if err != nil && !errors.Is(errors.Cause(err), apiv1.ErrAlreadyExists{ + Message: tk.Name + " already exists", + }) { + t.Errorf("PKCS11.GetPublicKey() error = %v", err) + } + } + + for i, c := range testCerts { + signer, err := k.CreateSigner(&apiv1.CreateSignerRequest{ + SigningKey: c.Key, + }) + if err != nil { + t.Errorf("PKCS11.CreateSigner() error = %v", err) + continue + } + cert, err := generateCertificate(signer.Public(), signer) + if err != nil { + t.Errorf("x509.CreateCertificate() error = %v", err) + continue + } + if err := k.StoreCertificate(&apiv1.StoreCertificateRequest{ + Name: c.Name, + Certificate: cert, + }); err != nil { + t.Errorf("PKCS1.StoreCertificate() error = %v", err) + continue + } + testCerts[i].Certificates = append(testCerts[i].Certificates, cert) + } +} + +func teardown(t *testing.T, k *PKCS11) { + for _, tk := range testKeys { + if err := k.DeleteKey(tk.Name); err != nil { + t.Errorf("PKCS11.DeleteKey() error = %v", err) + } + } + for _, tc := range testCerts { + if err := k.DeleteCertificate(tc.Name); err != nil { + t.Errorf("PKCS11.DeleteCertificate() error = %v", err) + } + } +} + +type setupFunc func(t *testing.T) *PKCS11 + +func setupFuncs(t *testing.T) (setupFunc, setupFunc) { + var sh2, yh2 *PKCS11 + t.Cleanup(func() { + if sh2 != nil { + sh2.Close() + } + if yh2 != nil { + yh2.Close() + } + }) + setupSoftHSM2 := func(t *testing.T) *PKCS11 { + if sh2 != nil { + return sh2 + } + sh2 = softHSM2(t) + return sh2 + } + setupYubiHSM2 := func(t *testing.T) *PKCS11 { + if yh2 != nil { + return yh2 + } + yh2 = yubiHSM2(t) + return yh2 + } + return setupSoftHSM2, setupYubiHSM2 +} + +// softHSM2 configures a *PKCS11 KMS to be used with softHSM2. To initialize +// this tests, we should run: +// softhsm2-util --init-token --free \ +// --token pkcs11-test --label pkcs11-test \ +// --so-pin password --pin password +// +// To delete we should run: +// softhsm2-util --delete-token --token pkcs11-test +func softHSM2(t *testing.T) *PKCS11 { + t.Helper() + if runtime.GOARCH != "amd64" { + t.Skipf("softHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH) + } + + var path string + switch runtime.GOOS { + case "darwin": + path = "/usr/local/lib/softhsm/libsofthsm2.so" + case "linux": + path = "/usr/lib/softhsm/libsofthsm2.so" + default: + t.Skipf("softHSM2 test skipped on %s", runtime.GOOS) + return nil + } + p11, err := crypto11.Configure(&crypto11.Config{ + Path: path, + TokenLabel: "pkcs11-test", + Pin: "password", + }) + if err != nil { + t.Skipf("softHSM test skipped on %s: %v", runtime.GOOS, err) + } + + k := &PKCS11{ + p11: p11, + } + + // Setup + softHSM2Once.Do(func() { + teardown(t, k) + setup(t, k) + }) + + return k +} + +// yubiHSM2 configures a *PKCS11 KMS to be used with YubiHSM2. To initialize +// this tests, we should run: +// yubihsm-connector -d +func yubiHSM2(t *testing.T) *PKCS11 { + t.Helper() + if runtime.GOARCH != "amd64" { + t.Skipf("yubiHSM2 test skipped on %s:%s", runtime.GOOS, runtime.GOARCH) + } + + var path string + switch runtime.GOOS { + case "darwin": + path = "/usr/local/lib/pkcs11/yubihsm_pkcs11.dylib" + case "linux": + path = "/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so" + default: + t.Skipf("yubiHSM2 test skipped on %s", runtime.GOOS) + return nil + } + p11, err := crypto11.Configure(&crypto11.Config{ + Path: path, + TokenLabel: "YubiHSM", + Pin: "0001password", + }) + if err != nil { + t.Skipf("yubiHSM2 test skipped on %s: %v", runtime.GOOS, err) + } + + k := &PKCS11{ + p11: p11, + } + + // Setup + yubiHSM2Once.Do(func() { + teardown(t, k) + setup(t, k) + }) + + return k +} diff --git a/kms/uri/uri.go b/kms/uri/uri.go index 85d512db..a5a4c55b 100644 --- a/kms/uri/uri.go +++ b/kms/uri/uri.go @@ -189,7 +189,9 @@ func StringDecode(s string) string { // HexDecode deocdes the string s using Percent-Encoding or regular hex // encoding. func HexDecode(s string) ([]byte, error) { - if strings.HasPrefix(s, "%") { + if s == "" { + return nil, nil + } else if strings.HasPrefix(s, "%") { return PercentDecode(s) }