Skip to content

Commit

Permalink
PKCS#11 test
Browse files Browse the repository at this point in the history
  • Loading branch information
e-asphyx committed Oct 2, 2024
1 parent f2bef3a commit 658869d
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 21 deletions.
1 change: 1 addition & 0 deletions cmd/signatory-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
_ "github.com/ecadlabs/signatory/pkg/vault/hashicorp"
_ "github.com/ecadlabs/signatory/pkg/vault/ledger"
_ "github.com/ecadlabs/signatory/pkg/vault/mem"
_ "github.com/ecadlabs/signatory/pkg/vault/pkcs11"
_ "github.com/ecadlabs/signatory/pkg/vault/yubi"
)

Expand Down
1 change: 1 addition & 0 deletions cmd/signatory/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
_ "github.com/ecadlabs/signatory/pkg/vault/hashicorp"
_ "github.com/ecadlabs/signatory/pkg/vault/ledger"
_ "github.com/ecadlabs/signatory/pkg/vault/mem"
_ "github.com/ecadlabs/signatory/pkg/vault/pkcs11"
_ "github.com/ecadlabs/signatory/pkg/vault/yubi"
)

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
cloud.google.com/go/kms v1.15.5
github.com/certusone/yubihsm-go v0.3.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/ecadlabs/go-pkcs11 v0.1.4
github.com/ecadlabs/go-pkcs11 v0.1.6
github.com/ecadlabs/goblst v1.0.0
github.com/ecadlabs/gotez/v2 v2.0.6
github.com/go-playground/validator/v10 v10.16.0
Expand Down Expand Up @@ -94,3 +94,4 @@ require (
)

// replace github.com/ecadlabs/gotez/v2 => ../gotez
// replace github.com/ecadlabs/go-pkcs11 => ../go-pkcs11
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/ecadlabs/go-pkcs11 v0.1.4 h1:o2WMEeoNCqU0WzVJG9stmH3anDOyfx8yRyFzJ5hMmZk=
github.com/ecadlabs/go-pkcs11 v0.1.4/go.mod h1:PwcQJwjNjjE0WtXtkTv4E0JNIcbl3cdIw+J+pRQbVjE=
github.com/ecadlabs/go-pkcs11 v0.1.6 h1:YnskuaCjLHoY9Y3sdUxB4POlTApsmZAdc0yw7AKPpUU=
github.com/ecadlabs/go-pkcs11 v0.1.6/go.mod h1:PwcQJwjNjjE0WtXtkTv4E0JNIcbl3cdIw+J+pRQbVjE=
github.com/ecadlabs/goblst v1.0.0 h1:8/e3SQGwqbV0+ul+pg0aSNFfC3lgQcvEed3VdDBXSl8=
github.com/ecadlabs/goblst v1.0.0/go.mod h1:s67gqaOol9o6fguh+evH75X5uQniOhv1HG/EU8xPLPY=
github.com/ecadlabs/gotez/v2 v2.0.6 h1:P7eQ2G+SO1tTV4NHnkdNlrOHxKDo1iF9m34HTLfS3b8=
Expand Down
94 changes: 94 additions & 0 deletions pkg/vault/pkcs11/pkcs11_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package pkcs11

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"

"github.com/ecadlabs/gotez/v2/crypt"
"github.com/ecadlabs/signatory/pkg/vault"
"github.com/stretchr/testify/require"
)

const (
libSoftHSMPathUnix = "/usr/lib/softhsm/libsofthsm2.so"
libSoftHSMPathMac = "/opt/homebrew/lib/softhsm/libsofthsm2.so"

userPIN = "1234"
soPIN = "1234"
keyLabel = "TestKey"
tokenLabel = "TestToken"
)

func TestPKCS11Vault(t *testing.T) {
var path string
if runtime.GOOS == "darwin" {
path = libSoftHSMPathMac
} else {
path = libSoftHSMPathUnix
}

if _, err := os.Stat(path); err != nil {
if errors.Is(err, os.ErrNotExist) {
t.Skipf("libsofthsm2 not installed, skipping testing")
}
t.Fatal(err)
}

if _, err := exec.LookPath("pkcs11-tool"); err != nil {
if errors.Is(err, exec.ErrNotFound) {
t.Skipf("pkcs11-tool not installed, skipping testing")
}
t.Fatal(err)
}

configPath := filepath.Join(t.TempDir(), "softhsm.conf")
tokensPath := t.TempDir()

fd, err := os.OpenFile(configPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
require.NoError(t, err)
fmt.Fprintf(fd, "directories.tokendir = %s\n", tokensPath)
fd.Close()

t.Setenv("SOFTHSM2_CONF", configPath)

out, err := exec.Command("pkcs11-tool", "--verbose", "--module", path, "--init-token", "--label", tokenLabel, "--so-pin", soPIN).CombinedOutput()
if err != nil {
t.Fatal(string(out))
}

out, err = exec.Command("pkcs11-tool", "--verbose", "--module", path, "--login", "--so-pin", soPIN, "--init-pin", "--pin", userPIN).CombinedOutput()
if err != nil {
t.Fatal(string(out))
}

out, err = exec.Command("pkcs11-tool", "--verbose", "--module", path, "--login", "--pin", userPIN, "--keypairgen", "--key-type", "EC:prime256v1", "--usage-sign", "--label", keyLabel).CombinedOutput()
if err != nil {
t.Fatal(string(out))
}

v, err := New(context.Background(), &Config{
LibraryPath: path,
Pin: userPIN,
Label: keyLabel,
})
require.NoError(t, err)
t.Cleanup(func() { v.Close() })

require.NoError(t, v.Unlock(context.Background()))

keys, err := vault.Collect(v.ListPublicKeys(context.Background()))
require.NoError(t, err)
require.Len(t, keys, 1)
k0 := keys[0].PublicKey().(*crypt.ECDSAPublicKey)

key, err := v.GetPublicKey(context.Background(), keys[0].ID())
require.NoError(t, err)
k1 := key.PublicKey().(*crypt.ECDSAPublicKey)
require.Equal(t, k0, k1)
}
96 changes: 78 additions & 18 deletions pkg/vault/pkcs11/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@ import (
"github.com/ecadlabs/signatory/pkg/errors"
"github.com/ecadlabs/signatory/pkg/vault"
"github.com/ecadlabs/signatory/pkg/vault/memory"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)

type PKCS11Vault struct {
module *pkcs11.Module
slot *pkcs11.Slot
info pkcs11.Info
info *pkcs11.ModuleInfo
conf Config
}

type Config struct {
LibraryPath string `yaml:"library_path"`
Pin string `yaml:"pin"`
Slot uint `yaml:"slot"`
Label string `yaml:"label"`
ObjectID string `yaml:"object_id"`
LibraryPath string
Pin string
Slot *uint
Label string
ObjectID []byte
}

type iterElem struct {
Expand Down Expand Up @@ -71,7 +72,7 @@ func (p *keyPair) PublicKey() crypt.PublicKey {
}

func (v *PKCS11Vault) formatError(err error) error {
return fmt.Errorf("(PKCS#11/%s %d.%d): %w", v.info.Manufacturer, v.info.Version.Major, v.info.Version.Minor, err)
return fmt.Errorf("(PKCS#11/%s %v): %w", v.info.Manufacturer, v.info.Version, err)
}

const (
Expand All @@ -93,14 +94,21 @@ func New(ctx context.Context, config *Config) (*PKCS11Vault, error) {
if conf.Label == "" {
conf.Label = os.Getenv(envLabel)
}
if conf.ObjectID == "" {
conf.ObjectID = os.Getenv(envObjID)
if conf.ObjectID == nil {
if v := os.Getenv(envObjID); v != "" {
id, err := hex.DecodeString(v)
if err != nil {
return nil, err
}
conf.ObjectID = id
}
}

module, err := pkcs11.Open(config.LibraryPath)
if err != nil {
return nil, fmt.Errorf("(PKCS#11/%s): %w", config.LibraryPath, err)
}
log.Debug(module.Info())
v := PKCS11Vault{
module: module,
info: module.Info(),
Expand All @@ -109,6 +117,13 @@ func New(ctx context.Context, config *Config) (*PKCS11Vault, error) {
return &v, nil
}

func (v *PKCS11Vault) Close() error {
if err := v.slot.Close(); err != nil {
return err
}
return v.module.Close()
}

type errIterator struct {
err error
}
Expand All @@ -130,12 +145,8 @@ func (v *PKCS11Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterat
if v.conf.Label != "" {
filter = append(filter, pkcs11.FilterLabel(v.conf.Label))
}
if v.conf.ObjectID != "" {
id, err := hex.DecodeString(v.conf.ObjectID)
if err != nil {
return errIterator{v.formatError(err)}
}
filter = append(filter, pkcs11.FilterID(id))
if v.conf.ObjectID != nil {
filter = append(filter, pkcs11.FilterID(v.conf.ObjectID))
}

objects, err := v.slot.Objects(filter...)
Expand Down Expand Up @@ -204,7 +215,28 @@ func (v *PKCS11Vault) SignMessage(ctx context.Context, msg []byte, key vault.Sto
}

func (v *PKCS11Vault) Unlock(ctx context.Context) error {
slot, err := v.module.Slot(v.conf.Slot, pkcs11.OptUserPIN(v.conf.Pin))
if v.conf.Slot == nil {
// use first slot with a token
slots, err := v.module.SlotIDs()
if err != nil {
return v.formatError(err)
}
for _, s := range slots {
si, err := v.module.SlotInfo(s)
if err != nil {
return v.formatError(err)
}
if si.Token != nil {
v.conf.Slot = &s
break
}
}
if v.conf.Slot == nil {
return v.formatError(errors.New("Token not found"))
}
}

slot, err := v.module.Slot(*v.conf.Slot, pkcs11.OptUserPIN(v.conf.Pin))
if err != nil {
return v.formatError(err)
}
Expand All @@ -216,15 +248,43 @@ func (c *PKCS11Vault) Name() string {
return "PKCS#11"
}

func (c *PKCS11Vault) VaultName() string {
return fmt.Sprintf("%s %v", c.info.Manufacturer, c.info.Version)
}

type yamlConfig struct {
LibraryPath string `yaml:"library_path"`
Pin string `yaml:"pin"`
Slot uint `yaml:"slot"`
Label string `yaml:"label"`
ObjectID string `yaml:"object_id"`
}

func init() {
vault.RegisterVault("pkcs11", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) {
var conf Config
var yamlConf yamlConfig
if node == nil {
return nil, errors.New("(PKCS#11): config is missing")
}
if err := node.Decode(&conf); err != nil {
if err := node.Decode(&yamlConf); err != nil {
return nil, err
}
conf := Config{
LibraryPath: yamlConf.LibraryPath,
Pin: yamlConf.Pin,
Slot: &yamlConf.Slot,
Label: yamlConf.Label,
}
if yamlConf.ObjectID != "" {
var err error
conf.ObjectID, err = hex.DecodeString(yamlConf.ObjectID)
if err != nil {
return nil, err
}
}

return New(ctx, &conf)
})
}

var _ vault.VaultNamer = (*PKCS11Vault)(nil)
20 changes: 20 additions & 0 deletions pkg/vault/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,24 @@ func Commands() []*cobra.Command {
return commands
}

func Collect(it StoredKeysIterator) ([]StoredKey, error) {
var keys []StoredKey
keyLoop:
for {
key, err := it.Next()
if err != nil {
switch {
case errors.Is(err, ErrDone):
break keyLoop
case errors.Is(err, ErrKey):
continue keyLoop
default:
return nil, err
}
}
keys = append(keys, key)
}
return keys, nil
}

var _ Factory = (registry)(nil)

0 comments on commit 658869d

Please sign in to comment.