diff --git a/kms/pkcs11/pkcs11.go b/kms/pkcs11/pkcs11.go index 46d0f962..838334de 100644 --- a/kms/pkcs11/pkcs11.go +++ b/kms/pkcs11/pkcs11.go @@ -12,11 +12,13 @@ import ( "encoding/hex" "fmt" "math/big" + "runtime" "strconv" "sync" "github.com/ThalesIgnite/crypto11" "github.com/pkg/errors" + "go.step.sm/crypto/kms/apiv1" "go.step.sm/crypto/kms/uri" ) @@ -50,34 +52,66 @@ type PKCS11 struct { closed sync.Once } -// New returns a new PKCS11 KMS. +// New returns a new PKCS#11 KMS. To initialize it, you need to provide a URI +// with the following format: +// +// - pkcs11:token=smallstep?pin-value=password +// - pkcs11:serial=1a2b3c4d5e6f?pin-source=/path/to/pin.txt +// - pkcs11:slot-id=5?pin-value=password +// - pkcs11:module-path=/path/to/module.so;token=smallstep?pin-value=password +// +// The scheme is "pkcs11"; "token", "serial", or "slot-id" defines the +// cryptographic device to use. "module-path" is the path of the PKCS#11 module +// to use. It will default to the proxy module of the p11-kit project if none is +// specified (p11-kit-proxy.so). "pin-value" provides the user's PIN, and +// "pin-source" defines a file that contains the PIN. +// +// A cryptographic key or object is identified by its "id" or "object" +// attributes. The "id" is the key identifier for the object, it's a hexadecimal +// string, and it will set the CKA_ID attribute of the object. The "object" is +// the name of the object, and it will set the CKA_LABEL attribute. Only one +// attribute is required to identify a key, but this package requires both to +// create a new key. The complete URI for a key looks like this: +// +// - pkcs11:token=smallstep;id=0a10;object=ec-key?pin-value=password +// - pkcs11:token=smallstep;id=%0a%10?pin-source=/path/to/pin.txt +// - pkcs11:token=smallstep;object=ec-key?pin-value=password func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) { + if opts.URI == "" { + return nil, errors.New("kms uri is required") + } + var config crypto11.Config - if opts.URI != "" { - u, err := uri.ParseWithScheme(Scheme, opts.URI) + u, err := uri.ParseWithScheme(Scheme, opts.URI) + if err != nil { + return nil, err + } + + config.TokenLabel = u.Get("token") + config.TokenSerial = u.Get("serial") + if v := u.Get("slot-id"); v != "" { + n, err := strconv.Atoi(v) if err != nil { - return nil, err + return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid") } + config.SlotNumber = &n + } - config.Pin = u.Pin() - config.Path = u.Get("module-path") - config.TokenLabel = u.Get("token") - config.TokenSerial = u.Get("serial") - if v := u.Get("slot-id"); v != "" { - n, err := strconv.Atoi(v) - if err != nil { - return nil, errors.Wrap(err, "kms uri 'slot-id' is not valid") - } - config.SlotNumber = &n - } + // Get module or default to use p11-kit-proxy.so. + // + // pkcs11.New(module string) will use dlopen that will look for the + // given library in the appropriate paths, so there's no need to provide + // the full path. + if config.Path = u.Get("module-path"); config.Path == "" { + config.Path = defaultModule } + + config.Pin = u.Pin() if config.Pin == "" && opts.Pin != "" { config.Pin = opts.Pin } switch { - case config.Path == "": - return nil, errors.New("kms uri 'module-path' are required") case config.TokenLabel == "" && config.TokenSerial == "" && config.SlotNumber == nil: return nil, errors.New("kms uri 'token', 'serial' or 'slot-id' are required") case config.Pin == "": @@ -100,13 +134,23 @@ func New(ctx context.Context, opts apiv1.Options) (*PKCS11, error) { }, nil } +// defaultModule defines the defaultModule used, in this case is the +// p11-kit-proxy provided by p11-kit. +var defaultModule = "p11-kit-proxy.so" + func init() { + switch runtime.GOOS { + case "darwin": + defaultModule = "p11-kit-proxy.dylib" + case "windows": + defaultModule = "p11-kit-proxy.dll" + } apiv1.Register(apiv1.PKCS11, func(ctx context.Context, opts apiv1.Options) (apiv1.KeyManager, error) { return New(ctx, opts) }) } -// GetPublicKey returns the public key .... +// GetPublicKey returns the public key stored in the object identified by the name URI. func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) { if req.Name == "" { return nil, errors.New("getPublicKeyRequest 'name' cannot be empty") diff --git a/kms/pkcs11/pkcs11_test.go b/kms/pkcs11/pkcs11_test.go index e6fcae9f..c196f7a3 100644 --- a/kms/pkcs11/pkcs11_test.go +++ b/kms/pkcs11/pkcs11_test.go @@ -25,15 +25,16 @@ import ( ) func TestNew(t *testing.T) { - tmp := p11Configure + tmp0 := p11Configure t.Cleanup(func() { - p11Configure = tmp + p11Configure = tmp0 }) k := mustPKCS11(t) t.Cleanup(func() { k.Close() }) + p11Configure = func(config *crypto11.Config) (P11, error) { if strings.Contains(config.Path, "fail") { return nil, errors.New("an error") @@ -68,10 +69,13 @@ func TestNew(t *testing.T) { URI: "pkcs11:module-path=/usr/local/lib/softhsm/libsofthsm2.so;token=pkcs11-test", Pin: "passowrd", }}, k, false}, - {"fail missing module", args{context.Background(), apiv1.Options{ + {"ok with missing module", args{context.Background(), apiv1.Options{ Type: "pkcs11", URI: "pkcs11:token=pkcs11-test", Pin: "passowrd", + }}, k, false}, + {"fail missing uri", args{context.Background(), apiv1.Options{ + Type: "pkcs11", }}, nil, true}, {"fail missing pin", args{context.Background(), apiv1.Options{ Type: "pkcs11", @@ -153,7 +157,7 @@ func TestPKCS11_GetPublicKey(t *testing.T) { Name: "pkcs11:id=7373;object=ecdsa-p256-key", }}, &ecdsa.PublicKey{}, false}, {"ECDSA by id", args{&apiv1.GetPublicKeyRequest{ - Name: "pkcs11:id=7373", + Name: "pkcs11:id=%73%73", }}, &ecdsa.PublicKey{}, false}, {"ECDSA by label", args{&apiv1.GetPublicKeyRequest{ Name: "pkcs11:object=ecdsa-p256-key", @@ -219,6 +223,15 @@ func TestPKCS11_CreateKey(t *testing.T) { SigningKey: testObject, }, }, false}, + {"default with percent URI", args{&apiv1.CreateKeyRequest{ + Name: testObjectPercent, + }}, &apiv1.CreateKeyResponse{ + Name: testObjectPercent, + PublicKey: &ecdsa.PublicKey{}, + CreateSignerRequest: apiv1.CreateSignerRequest{ + SigningKey: testObjectPercent, + }, + }, false}, {"RSA SHA256WithRSA", args{&apiv1.CreateKeyRequest{ Name: testObject, SignatureAlgorithm: apiv1.SHA256WithRSA, @@ -424,7 +437,7 @@ func TestPKCS11_CreateSigner(t *testing.T) { SigningKey: "pkcs11:id=7371;object=rsa-key", }}, apiv1.SHA256WithRSA, crypto.SHA256, false}, {"RSA PSS", args{&apiv1.CreateSignerRequest{ - SigningKey: "pkcs11:id=7372;object=rsa-pss-key", + SigningKey: "pkcs11:id=%73%72;object=rsa-pss-key", }}, apiv1.SHA256WithRSAPSS, &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: crypto.SHA256, @@ -433,7 +446,7 @@ func TestPKCS11_CreateSigner(t *testing.T) { SigningKey: "pkcs11:id=7373;object=ecdsa-p256-key", }}, apiv1.ECDSAWithSHA256, crypto.SHA256, false}, {"ECDSA P384", args{&apiv1.CreateSignerRequest{ - SigningKey: "pkcs11:id=7374;object=ecdsa-p384-key", + SigningKey: "pkcs11:id=%73%74;object=ecdsa-p384-key", }}, apiv1.ECDSAWithSHA384, crypto.SHA384, false}, {"ECDSA P521", args{&apiv1.CreateSignerRequest{ SigningKey: "pkcs11:id=7375;object=ecdsa-p521-key", @@ -508,7 +521,7 @@ func TestPKCS11_CreateDecrypter(t *testing.T) { DecryptionKey: "pkcs11:id=7371;object=rsa-key", }}, false}, {"RSA PSS", args{&apiv1.CreateDecrypterRequest{ - DecryptionKey: "pkcs11:id=7372;object=rsa-pss-key", + DecryptionKey: "pkcs11:id=%73%72;object=rsa-pss-key", }}, false}, {"ECDSA P256", args{&apiv1.CreateDecrypterRequest{ DecryptionKey: "pkcs11:id=7373;object=ecdsa-p256-key", diff --git a/kms/pkcs11/setup_test.go b/kms/pkcs11/setup_test.go index 9114d196..3d83c441 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" + testObjectPercent = "pkcs11:id=%73%70;object=test-name" testObjectAlt = "pkcs11:id=7377;object=alt-test-name" testObjectByID = "pkcs11:id=7370" testObjectByLabel = "pkcs11:object=test-name" @@ -27,9 +28,9 @@ var ( Bits int }{ {"pkcs11:id=7371;object=rsa-key", apiv1.SHA256WithRSA, 2048}, - {"pkcs11:id=7372;object=rsa-pss-key", apiv1.SHA256WithRSAPSS, DefaultRSASize}, + {"pkcs11:id=%73%72;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=%73%74;object=ecdsa-p384-key", apiv1.ECDSAWithSHA384, 0}, {"pkcs11:id=7375;object=ecdsa-p521-key", apiv1.ECDSAWithSHA512, 0}, }