Add initial set of unit tests for pkcs11 kms.

This commit is contained in:
Mariano Cano 2021-01-27 20:17:14 -08:00
parent a6c784d5dd
commit 294f84b8d4
5 changed files with 945 additions and 36 deletions

View file

@ -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

View file

@ -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

View file

@ -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)
}
}
})
}
}

235
kms/pkcs11/setup_test.go Normal file
View file

@ -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
}

View file

@ -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)
}