diff --git a/.gitignore b/.gitignore index 4864a9bf..beb5f9ae 100644 --- a/.gitignore +++ b/.gitignore @@ -22,12 +22,11 @@ dist .release-env .env .docker-creds -./signatory -./signatory-cli +/signatory +/signatory-cli .DS_Store # some integration_tests write secret env var to files -integration_test/gcp-token.json -integration_test/.env.vaults.cicd -integration_test/service-principal.key -**vault** \ No newline at end of file +/integration_test/gcp-token.json +/integration_test/.env.vaults.cicd +/integration_test/service-principal.key \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 2371e35a..c0ff85b0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -198,43 +198,43 @@ builds: - '{{ if .IsSnapshot }}-cover{{ else }}-v{{ end }}' # WINDOWS - - id: signatory-windows-amd - binary: signatory - env: - - CGO_ENABLED=1 - - CC=x86_64-w64-mingw32-gcc - - CXX=x86_64-w64-mingw32-g++ - - >- - {{- if .IsSnapshot }}GOEXPERIMENT=coverageredesign{{- end }} - main: ./cmd/signatory/main.go - ldflags: - - '-X github.com/ecadlabs/signatory/pkg/metrics.GitRevision={{.Version}}' - - '-X github.com/ecadlabs/signatory/pkg/metrics.GitBranch={{.Version}}' - goos: - - windows - goarch: - - amd64 - flags: - - '{{ if .IsSnapshot }}-cover{{ else }}-v{{ end }}' + # - id: signatory-windows-amd + # binary: signatory + # env: + # - CGO_ENABLED=1 + # - CC=x86_64-w64-mingw32-gcc + # - CXX=x86_64-w64-mingw32-g++ + # - >- + # {{- if .IsSnapshot }}GOEXPERIMENT=coverageredesign{{- end }} + # main: ./cmd/signatory/main.go + # ldflags: + # - '-X github.com/ecadlabs/signatory/pkg/metrics.GitRevision={{.Version}}' + # - '-X github.com/ecadlabs/signatory/pkg/metrics.GitBranch={{.Version}}' + # goos: + # - windows + # goarch: + # - amd64 + # flags: + # - '{{ if .IsSnapshot }}-cover{{ else }}-v{{ end }}' - - id: signatory-cli-windows-amd - binary: signatory-cli - env: - - CGO_ENABLED=1 - - CC=x86_64-w64-mingw32-gcc - - CXX=x86_64-w64-mingw32-g++ - - >- - {{- if .IsSnapshot }}GOEXPERIMENT=coverageredesign{{- end }} - main: ./cmd/signatory-cli/main.go - ldflags: - - '-X github.com/ecadlabs/signatory/pkg/metrics.GitRevision={{.Version}}' - - '-X github.com/ecadlabs/signatory/pkg/metrics.GitBranch={{.Version}}' - goos: - - windows - goarch: - - amd64 - flags: - - '{{ if .IsSnapshot }}-cover{{ else }}-v{{ end }}' + # - id: signatory-cli-windows-amd + # binary: signatory-cli + # env: + # - CGO_ENABLED=1 + # - CC=x86_64-w64-mingw32-gcc + # - CXX=x86_64-w64-mingw32-g++ + # - >- + # {{- if .IsSnapshot }}GOEXPERIMENT=coverageredesign{{- end }} + # main: ./cmd/signatory-cli/main.go + # ldflags: + # - '-X github.com/ecadlabs/signatory/pkg/metrics.GitRevision={{.Version}}' + # - '-X github.com/ecadlabs/signatory/pkg/metrics.GitBranch={{.Version}}' + # goos: + # - windows + # goarch: + # - amd64 + # flags: + # - '{{ if .IsSnapshot }}-cover{{ else }}-v{{ end }}' dockers: - ids: diff --git a/README.md b/README.md index 5a7b15e8..d9164a10 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ In the first year of the Tezos network operation, there was anecdotal evidence t | AWS KMS | ✅ | | Ledger Nano S/S+ (Baking only) | ✅ | | Hashicorp Vault | ✅ | +| PKCS#11 | ✅ | ### Tezos Address Types @@ -90,6 +91,7 @@ In Tezos, you can infer the signing algorithm from the first three characters of | AWS KMS | ❌ | ✅ | ✅ | | Azure KMS | ❌ | ✅ | ✅ | | YubiHSM2 | ✅ | ✅ | ✅ | +| PKCS#11 | ✅ | ✅ | ✅ | --- diff --git a/cmd/signatory-cli/main.go b/cmd/signatory-cli/main.go index 3b50f530..3e036b27 100644 --- a/cmd/signatory-cli/main.go +++ b/cmd/signatory-cli/main.go @@ -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" ) diff --git a/cmd/signatory/main.go b/cmd/signatory/main.go index ae9048f0..593246f2 100644 --- a/cmd/signatory/main.go +++ b/cmd/signatory/main.go @@ -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" ) diff --git a/docs/pkcs11.md b/docs/pkcs11.md new file mode 100644 index 00000000..fdc82aa7 --- /dev/null +++ b/docs/pkcs11.md @@ -0,0 +1,36 @@ +--- +id: pkcs11 +title: PKCS#11 +--- + +# PKCS#11 Vault + +## Configuration + +||||| +|--- |--- |--- |--- | +|Name|Type|Required|Description| +|library_path|string|✅|Library Path| +|pin|string|✅|User PIN| +|slot|string||Slot ID| +|label|string||Limit key search to the specified label (use in case of multiple key pairs in the same token)| +|object_ih|hex||Limit key search to the specified object ID (use in case of multiple key pairs in the same token)| + +**Note**: If the token contains multiple key pairs, every pair must have unique label or ID shared between private and public parts. + +### Example + +```yaml +library_path: /opt/homebrew/lib/softhsm/libsofthsm2.so +pin: 1234 +slot: 0x4d0b85a2 +label: TestKey +``` + +## Environment variables + +* `PKCS11_PATH` +* `PKCS11_PIN` +* `PKCS11_SLOT` +* `PKCS11_LABEL` +* `PKCS11_OBJECT_ID` diff --git a/docs/start.md b/docs/start.md index b4abf938..d4c1adee 100644 --- a/docs/start.md +++ b/docs/start.md @@ -168,6 +168,7 @@ Currently three backends are supported: `file` (a default one), `mem` (for testi * [GCP Key Management](gcp_kms.md) * [YubiHSM2](yubihsm.md) * [Hashicorp Vault](hashicorp_vault.md) +* [PKCS#11](pkcs11.md) --- diff --git a/go.mod b/go.mod index d2db0e23..717ad24c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 github.com/aws/smithy-go v1.20.3 github.com/certusone/yubihsm-go v0.3.0 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 + github.com/ecadlabs/go-pkcs11 v0.2.1 github.com/ecadlabs/goblst v1.0.0 github.com/ecadlabs/gotez/v2 v2.1.3 github.com/go-playground/validator/v10 v10.22.0 @@ -27,7 +28,7 @@ require ( github.com/segmentio/ksuid v1.0.4 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e golang.org/x/oauth2 v0.15.0 @@ -110,3 +111,4 @@ require ( ) // replace github.com/ecadlabs/gotez/v2 => ../gotez +// replace github.com/ecadlabs/go-pkcs11 => ../go-pkcs11 diff --git a/go.sum b/go.sum index edb05038..a0ca3317 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +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.2.1 h1:/3qAVS+lTNyrlBYXq6lq+tmp9fz24/5YSY3bVzTuMsg= +github.com/ecadlabs/go-pkcs11 v0.2.1/go.mod h1:PwAVBY0muwp6quQFmSDcB5Ekl4TjGG7cEQQwY9KpNVc= 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.1.3 h1:RGNtvb+UAtstTQYCsdE4XAeaEZwj3a5AliLluEOsoAg= @@ -242,8 +244,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= diff --git a/pkg/vault/memory/vault.go b/pkg/vault/memory/vault.go index 619c1290..4a0d25bc 100644 --- a/pkg/vault/memory/vault.go +++ b/pkg/vault/memory/vault.go @@ -19,6 +19,10 @@ type PrivateKey struct { KeyID string } +func (p *PrivateKey) Elem() (key vault.StoredKey, err error) { + return p, nil +} + // PublicKey get the public key associated with this key func (f *PrivateKey) PublicKey() crypt.PublicKey { return f.PrivateKey.Public() @@ -44,18 +48,26 @@ type Vault struct { unlocked bool } -type iterator struct { - keys []*PrivateKey +type IteratorElem interface { + Elem() (key vault.StoredKey, err error) +} + +type Iterator[T IteratorElem] struct { + keys []T idx int } -func (i *iterator) Next() (key vault.StoredKey, err error) { +func NewIterator[T IteratorElem](keys []T) *Iterator[T] { + return &Iterator[T]{keys: keys} +} + +func (i *Iterator[T]) Next() (key vault.StoredKey, err error) { if i.idx == len(i.keys) { return nil, vault.ErrDone } - key = i.keys[i.idx] + key, err = i.keys[i.idx].Elem() i.idx++ - return key, nil + return key, err } // NewUnparsed create a new in-mempory vault from Tezos encoded data. Call Unlock before use @@ -108,7 +120,7 @@ func New(src []*PrivateKey, name string) (*Vault, error) { func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { v.mtx.Lock() defer v.mtx.Unlock() - return &iterator{keys: v.keys} + return &Iterator[*PrivateKey]{keys: v.keys} } // GetPublicKey retrieve a public key diff --git a/pkg/vault/pkcs11/pkcs11_test.go b/pkg/vault/pkcs11/pkcs11_test.go new file mode 100644 index 00000000..07b46094 --- /dev/null +++ b/pkg/vault/pkcs11/pkcs11_test.go @@ -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) +} diff --git a/pkg/vault/pkcs11/vault.go b/pkg/vault/pkcs11/vault.go new file mode 100644 index 00000000..8b558fa5 --- /dev/null +++ b/pkg/vault/pkcs11/vault.go @@ -0,0 +1,295 @@ +package pkcs11 + +import ( + "context" + "encoding/hex" + stderr "errors" + "fmt" + "net/http" + "os" + "strconv" + + "github.com/ecadlabs/go-pkcs11/pkcs11" + "github.com/ecadlabs/gotez/v2/crypt" + "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 + session *pkcs11.Session + info *pkcs11.ModuleInfo + conf Config +} + +type Config struct { + LibraryPath string + Pin string + Slot *uint + Label string + ObjectID []byte +} + +type iterElem struct { + obj *pkcs11.Object + v *PKCS11Vault +} + +func (i *iterElem) Elem() (vault.StoredKey, error) { + pk, err := i.obj.PrivateKey() + if err != nil { + return nil, i.v.formatError(err) + } + + switch pk.(type) { + case *pkcs11.ECDSAPrivateKey, *pkcs11.Ed25519PrivateKey: + kp, err := pk.KeyPair(pkcs11.MatchLabel | pkcs11.MatchID) + if err != nil { + return nil, i.v.formatError(err) + } + key := &keyPair{ + obj: i.obj, + kp: kp, + } + return key, nil + default: + return nil, vault.ErrKey + } +} + +type keyPair struct { + obj *pkcs11.Object + kp pkcs11.KeyPair +} + +func (p *keyPair) ID() string { + return fmt.Sprintf("%08x", p.obj.Handle()) +} + +func (p *keyPair) PublicKey() crypt.PublicKey { + pub, err := crypt.NewPublicKeyFrom(p.kp.Public()) + if err != nil { + panic(err) // shouldn't happen + } + return pub +} + +func (v *PKCS11Vault) formatError(err error) error { + return fmt.Errorf("(PKCS#11/%s %v): %w", v.info.Manufacturer, v.info.Version, err) +} + +const ( + envLibraryPath = "PKCS11_PATH" + envPin = "PKCS11_PIN" + envSlot = "PKCS11_SLOT" + envLabel = "PKCS11_LABEL" + envObjID = "PKCS11_OBJECT_ID" +) + +func New(ctx context.Context, config *Config) (*PKCS11Vault, error) { + conf := *config + if conf.LibraryPath == "" { + conf.LibraryPath = os.Getenv(envLibraryPath) + } + if conf.Pin == "" { + conf.Pin = os.Getenv(envPin) + } + if conf.Label == "" { + conf.Label = os.Getenv(envLabel) + } + 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(), + conf: conf, + } + return &v, nil +} + +func (v *PKCS11Vault) Close() error { + if err := v.session.Close(); err != nil { + return err + } + return v.module.Close() +} + +type errIterator struct { + err error +} + +func (e errIterator) Next() (vault.StoredKey, error) { + return nil, e.err +} + +// GetPublicKey returns a public key by given ID +func (v *PKCS11Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { + if v.session == nil { + return errIterator{v.formatError(errors.New("locked"))} + } + + filter := []pkcs11.Filter{ + pkcs11.FilterClass(pkcs11.ClassPrivateKey), + } + if v.conf.Label != "" { + filter = append(filter, pkcs11.FilterLabel(v.conf.Label)) + } + if v.conf.ObjectID != nil { + filter = append(filter, pkcs11.FilterID(v.conf.ObjectID)) + } + + objects, err := v.session.Objects(filter...) + if err != nil { + return errIterator{v.formatError(err)} + } + + elems := make([]*iterElem, len(objects)) + for i, o := range objects { + elems[i] = &iterElem{ + obj: o, + v: v, + } + } + return memory.NewIterator(elems) +} + +func (v *PKCS11Vault) GetPublicKey(ctx context.Context, id string) (vault.StoredKey, error) { + if v.session == nil { + return nil, v.formatError(errors.New("locked")) + } + handle, err := strconv.ParseUint(id, 16, 32) + if err != nil { + return nil, v.formatError(err) + } + obj, err := v.session.NewObject(uint(handle)) + if err != nil { + if stderr.Is(err, pkcs11.ErrObjectHandleInvalid) { + return nil, errors.Wrap(v.formatError(err), http.StatusNotFound) + } + return nil, v.formatError(err) + } + pk, err := obj.PrivateKey() + if err != nil { + return nil, v.formatError(err) + } + kp, err := pk.KeyPair(pkcs11.MatchLabel | pkcs11.MatchID) + if err != nil { + return nil, v.formatError(err) + } + key := &keyPair{ + obj: obj, + kp: kp, + } + return key, nil +} + +func (v *PKCS11Vault) SignMessage(ctx context.Context, msg []byte, key vault.StoredKey) (crypt.Signature, error) { + if v.session == nil { + return nil, v.formatError(errors.New("locked")) + } + kp, ok := key.(*keyPair) + if !ok { + return nil, v.formatError(fmt.Errorf("invalid key type %T", key)) + } + digest := crypt.DigestFunc(msg) + sig, err := kp.kp.Sign(nil, digest[:], nil) + if err != nil { + return nil, v.formatError(err) + } + ret, err := crypt.NewSignatureFromBytes(sig, kp.PublicKey()) + if err != nil { + return nil, v.formatError(err) + } + return ret, nil +} + +func (v *PKCS11Vault) Unlock(ctx context.Context) error { + 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")) + } + } + + session, err := v.module.NewSession(*v.conf.Slot, pkcs11.OptUserPIN(v.conf.Pin)) + if err != nil { + return v.formatError(err) + } + v.session = session + return nil +} + +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 yamlConf yamlConfig + if node == nil { + return nil, errors.New("(PKCS#11): config is missing") + } + 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) diff --git a/pkg/vault/vault.go b/pkg/vault/vault.go index 1f770b5d..f879e800 100644 --- a/pkg/vault/vault.go +++ b/pkg/vault/vault.go @@ -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)