Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automatic support for p11-kit #260

Merged
merged 8 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 62 additions & 18 deletions kms/pkcs11/pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
maraino marked this conversation as resolved.
Show resolved Hide resolved
// to use, it will default to the proxy module of the p11-kit project if none is
maraino marked this conversation as resolved.
Show resolved Hide resolved
// specified (p11-kit-proxy.so), "pin-value" provides the user's PIN, and
maraino marked this conversation as resolved.
Show resolved Hide resolved
// "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
maraino marked this conversation as resolved.
Show resolved Hide resolved
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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could include the value of slot-id

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wrapped error contains the string.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

}
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 == "":
Expand All @@ -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"
maraino marked this conversation as resolved.
Show resolved Hide resolved
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 in stored in the given object.
maraino marked this conversation as resolved.
Show resolved Hide resolved
func (k *PKCS11) GetPublicKey(req *apiv1.GetPublicKeyRequest) (crypto.PublicKey, error) {
if req.Name == "" {
return nil, errors.New("getPublicKeyRequest 'name' cannot be empty")
Expand Down
27 changes: 20 additions & 7 deletions kms/pkcs11/pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions kms/pkcs11/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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},
}

Expand Down