Skip to content

Commit

Permalink
PKCS#11 Vault (#501)
Browse files Browse the repository at this point in the history
* PKCS#11 untested

* PKCS#11 test

* PKCS#11 doc

* missing docs

* limit PKCS#11 handle parsing to 32 bit

* Ed25519 support for PKCS#11 backend

* go-pkcs11 v0.2.1

* chore: remove windows builds from goreleaser

---------

Co-authored-by: GImbrailo <[email protected]>
  • Loading branch information
e-asphyx and GImbrailo authored Oct 29, 2024
1 parent cb93f83 commit 1b20028
Show file tree
Hide file tree
Showing 13 changed files with 520 additions and 54 deletions.
11 changes: 5 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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**
/integration_test/gcp-token.json
/integration_test/.env.vaults.cicd
/integration_test/service-principal.key
72 changes: 36 additions & 36 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

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

---

Expand Down
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
36 changes: 36 additions & 0 deletions docs/pkcs11.md
Original file line number Diff line number Diff line change
@@ -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`
1 change: 1 addition & 0 deletions docs/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

---

Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -110,3 +111,4 @@ require (
)

// replace github.com/ecadlabs/gotez/v2 => ../gotez
// replace github.com/ecadlabs/go-pkcs11 => ../go-pkcs11
11 changes: 7 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
24 changes: 18 additions & 6 deletions pkg/vault/memory/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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
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)
}
Loading

0 comments on commit 1b20028

Please sign in to comment.