diff --git a/cmd/approve-list-svc/server/server_test.go b/cmd/approve-list-svc/server/server_test.go index 46938b65..703279ff 100644 --- a/cmd/approve-list-svc/server/server_test.go +++ b/cmd/approve-list-svc/server/server_test.go @@ -62,7 +62,7 @@ func testServer(t *testing.T, addr []net.IP) error { VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { - PrivateKey: signPriv, + Key: signPriv, }, }, "Mock") }), diff --git a/cmd/commands/utils.go b/cmd/commands/utils.go index 6aa6050d..c1242b5c 100644 --- a/cmd/commands/utils.go +++ b/cmd/commands/utils.go @@ -6,12 +6,13 @@ import ( "text/template" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/vault" ) const listTemplateSrc = `{{range . -}} -Public Key Hash: {{.PublicKeyHash}} -Vault: {{.VaultName}} -ID: {{.ID}} +Public Key Hash: {{.Hash}} +Reference: {{keyRef .KeyReference}} +Vault: {{.Vault.Name}} Active: {{.Active}} {{with .Policy -}} Allowed Requests: {{.AllowedRequests}} @@ -21,7 +22,14 @@ Allowed Operations: {{.AllowedOps}} ` var ( - listTpl = template.Must(template.New("list").Parse(listTemplateSrc)) + listTpl = template.Must(template.New("list").Funcs(template.FuncMap{ + "keyRef": func(ref vault.KeyReference) string { + if withID, ok := ref.(vault.WithID); ok { + return withID.ID() + } + return "" + }, + }).Parse(listTemplateSrc)) ) func listKeys(s *signatory.Signatory, w io.Writer, ctx context.Context) error { diff --git a/cmd/signatory-cli/main.go b/cmd/signatory-cli/main.go index 3e036b27..fd35c4f1 100644 --- a/cmd/signatory-cli/main.go +++ b/cmd/signatory-cli/main.go @@ -12,15 +12,7 @@ import ( "github.com/ecadlabs/signatory/pkg/vault" // Install backends - _ "github.com/ecadlabs/signatory/pkg/vault/aws" - _ "github.com/ecadlabs/signatory/pkg/vault/azure" - _ "github.com/ecadlabs/signatory/pkg/vault/cloudkms" - _ "github.com/ecadlabs/signatory/pkg/vault/file" - _ "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" + _ "github.com/ecadlabs/signatory/pkg/vault/preamble" ) func newRootCommand(ctx context.Context) *cobra.Command { diff --git a/cmd/signatory/main.go b/cmd/signatory/main.go index 593246f2..9cc1f9eb 100644 --- a/cmd/signatory/main.go +++ b/cmd/signatory/main.go @@ -11,15 +11,7 @@ import ( "github.com/spf13/cobra" // Install backends - _ "github.com/ecadlabs/signatory/pkg/vault/aws" - _ "github.com/ecadlabs/signatory/pkg/vault/azure" - _ "github.com/ecadlabs/signatory/pkg/vault/cloudkms" - _ "github.com/ecadlabs/signatory/pkg/vault/file" - _ "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" + _ "github.com/ecadlabs/signatory/pkg/vault/preamble" ) func newRootCommand(ctx context.Context) *cobra.Command { diff --git a/docs/pkcs11.md b/docs/pkcs11.md index fdc82aa7..73dfacde 100644 --- a/docs/pkcs11.md +++ b/docs/pkcs11.md @@ -7,30 +7,77 @@ title: PKCS#11 ## 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)| +| Field | Type | Required | Description | +| -------------------------- | ---------------------------------- | -------- | ------------------------------------------------------------ | +| library_path | string | ✅ | Library path. If not specified then `PKCS11_PATH` environment variable value will be used instead. | +| slot | unsigned integer | | Slot ID. Is both the field and `PKCS11_SLOT` environment variable are missed then the first slot with an initialised token will be used. | +| pin | string | ✅ | User PIN. If not specified then `PKCS11_PIN` environment variable value will be used instead. | +| keys | sequence of `Key Pair` (see below) | | Key list. Use all available keys if not specified (see `public_keys_search_options` description) | +| public_keys_search_options | | | Automatic key pair discovery options (see below) | -**Note**: If the token contains multiple key pairs, every pair must have unique label or ID shared between private and public parts. +### Key Pair -### Example +| Field | Type | Required | Description | +| ---------------- | ------------------------ | -------- | ------------------------------------------------------------ | +| private | `Key Config` (see below) | | Private key locator. | +| public | `Key Config` | | Public key locator. | +| public_value | Base58 string | | Public key value. | +| extended_private | boolean | | Try to read the public key data from the private key object. In some PKCS#11 implementations private key objects have `EC_POINT` attribute. | + +**Note**: `public_value` takes precedence over `public`. If none of `public` and `public_value` fields are present then the private key locator `Key Config` will be reused. + +### Key Config + +| Field | Type | Required | Description | +| ----- | ------ | -------- | ------------ | +| label | string | | Object label | +| id | hex | | Object ID | + +### Public Keys Search Options + +| Field | Type | Required | Description | +| ---------------- | ------- | -------- | ------------------------------------------------------------ | +| match_label | boolean | | Find the corresponding public key by matching label. | +| match_id | boolean | | Find the corresponding public key by matching ID. | +| extended_private | boolean | | Try to read the public key data from the private key object. | + +**Note**: if the whole object is missing then all options will be assumed as **true** + +### Environment Variables + +| Variable | Description | +| ----------- | ------------ | +| PKCS11_PATH | Library path | +| PKCS11_SLOT | Slot ID | +| PKCS11_PIN | User PIN | + +## Examples + +### Automatic discovery ```yaml -library_path: /opt/homebrew/lib/softhsm/libsofthsm2.so -pin: 1234 -slot: 0x4d0b85a2 -label: TestKey +library_path: /usr/lib/hsmdriver/libhsmdriver.so +pin: user_pin ``` -## Environment variables +### Manual Configuration -* `PKCS11_PATH` -* `PKCS11_PIN` -* `PKCS11_SLOT` -* `PKCS11_LABEL` -* `PKCS11_OBJECT_ID` +```yaml +library_path: /usr/lib/hsmdriver/libhsmdriver.so +slot: 0 +pin: user_pin + keys: + - private: + label: PrivateKey0 + public: + label: PublicKey0 + - private: + label: Key1 + # Use public key with the same label `Key1' + - private: + id: 1234abcd + public_value: edpkuXdPrbYEu5x54NaZEzaSHzwi5Tis5NBHrs58AMJXf4gS4iz5eQ + - private: + label: Key2 + extended_private: true # Read the public key from the private object +``` diff --git a/go.mod b/go.mod index 717ad24c..a23095bc 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/aws/smithy-go v1.20.3 github.com/certusone/yubihsm-go v0.3.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 - github.com/ecadlabs/go-pkcs11 v0.2.1 + github.com/ecadlabs/go-pkcs11 v0.3.0 github.com/ecadlabs/goblst v1.0.0 github.com/ecadlabs/gotez/v2 v2.1.3 github.com/go-playground/validator/v10 v10.22.0 @@ -29,7 +29,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.29.0 golang.org/x/exp v0.0.0-20231127185646-65229373498e golang.org/x/oauth2 v0.15.0 google.golang.org/api v0.152.0 @@ -75,7 +75,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/sync v0.9.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 // indirect @@ -102,9 +102,9 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 - golang.org/x/text v0.19.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 + golang.org/x/text v0.20.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index a0ca3317..13ad911e 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,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.2.1 h1:/3qAVS+lTNyrlBYXq6lq+tmp9fz24/5YSY3bVzTuMsg= -github.com/ecadlabs/go-pkcs11 v0.2.1/go.mod h1:PwAVBY0muwp6quQFmSDcB5Ekl4TjGG7cEQQwY9KpNVc= +github.com/ecadlabs/go-pkcs11 v0.3.0 h1:AsLURdNoZn0YocumJFloWXIlx1f2SDw4eTx4nPMa7II= +github.com/ecadlabs/go-pkcs11 v0.3.0/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= @@ -244,7 +244,6 @@ 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.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= @@ -256,8 +255,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= @@ -285,8 +284,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -302,20 +301,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= diff --git a/pkg/server/server.go b/pkg/server/server.go index d080e025..112f06a6 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -152,7 +152,7 @@ func (s *Server) getKeyHandler(w http.ResponseWriter, r *http.Request) { resp := struct { PublicKey crypt.PublicKey `json:"public_key"` }{ - PublicKey: key.PublicKey, + PublicKey: key.PublicKey(), } jsonResponse(w, http.StatusOK, &resp) } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 9f37da42..88e9d1f6 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -13,6 +13,7 @@ import ( "github.com/ecadlabs/gotez/v2/crypt" "github.com/ecadlabs/signatory/pkg/server" "github.com/ecadlabs/signatory/pkg/signatory" + "github.com/ecadlabs/signatory/pkg/vault" "github.com/stretchr/testify/require" ) @@ -130,6 +131,17 @@ func TestSign(t *testing.T) { } } +type mockRef struct { + key crypt.PublicKey +} + +func (k *mockRef) PublicKey() crypt.PublicKey { return k.key } +func (k *mockRef) String() string { return k.key.Hash().String() } +func (k *mockRef) Vault() vault.Vault { panic("not implemented") } +func (k *mockRef) Sign(ctx context.Context, message []byte) (crypt.Signature, error) { + panic("not implemented") +} + func TestGetPublicKey(t *testing.T) { type testCase struct { Name string @@ -157,7 +169,7 @@ func TestGetPublicKey(t *testing.T) { { Name: "Normal", StatusCode: http.StatusOK, - Response: &signatory.PublicKey{PublicKey: mustPk(&tz.Ed25519PublicKey{1, 2, 3})}, + Response: &signatory.PublicKey{KeyReference: &mockRef{mustPk(&tz.Ed25519PublicKey{1, 2, 3})}}, Expected: "{\"public_key\":\"edpktefgU4dfKqN1rZVBwBP8ZueBoJZfhDS3kHPSbo8c3aGPrMrunt\"}\n", }, } diff --git a/pkg/signatory/import.go b/pkg/signatory/import.go index a4e9a849..60bfac15 100644 --- a/pkg/signatory/import.go +++ b/pkg/signatory/import.go @@ -41,27 +41,21 @@ func (s *Signatory) Import(ctx context.Context, importerName string, secretKey s logPKH: hash, logVault: importer.Name(), }) - if n, ok := importer.(vault.VaultNamer); ok { - l = l.WithField(logVaultName, n.VaultName()) - } else { - l = l.WithField(logVaultName, importerName) - } - l.Info("Requesting import operation") - stored, err := importer.Import(ctx, priv, opt) + ref, err := importer.Import(ctx, priv, opt) if err != nil { return nil, err } - s.cache.push(&keyVaultPair{pkh: hash, key: stored, vault: importer}) + s.cache.push(&keyVaultPair{pkh: hash, key: ref}) - l.WithField(logKeyID, stored.ID()).Info("Successfully imported") + l.WithField(logPKH, hash).Info("Successfully imported") + pol := s.fetchPolicyOrDefault(hash) return &PublicKey{ - PublicKey: pub, - PublicKeyHash: hash, - VaultName: importer.Name(), - ID: stored.ID(), - Policy: s.fetchPolicyOrDefault(hash), + KeyReference: ref, + Hash: hash, + Policy: s.fetchPolicyOrDefault(hash), + Active: pol != nil, }, nil } diff --git a/pkg/signatory/policy_hook_test.go b/pkg/signatory/policy_hook_test.go index df7aacd8..7ccdb6db 100644 --- a/pkg/signatory/policy_hook_test.go +++ b/pkg/signatory/policy_hook_test.go @@ -104,8 +104,7 @@ func testPolicyHookAuth(t *testing.T, status int) error { VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { - PrivateKey: signPriv, - KeyID: signKeyHash.String(), + Key: signPriv, }, }, "Mock") }), @@ -143,8 +142,7 @@ func testPolicyHook(t *testing.T, status int) error { VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { return memory.New([]*memory.PrivateKey{ { - PrivateKey: signPriv, - KeyID: signKeyHash.String(), + Key: signPriv, }, }, "Mock") }), diff --git a/pkg/signatory/signatory.go b/pkg/signatory/signatory.go index d311ab87..e84043e4 100644 --- a/pkg/signatory/signatory.go +++ b/pkg/signatory/signatory.go @@ -74,12 +74,10 @@ type PublicKeyPolicy struct { // PublicKey contains public key with its hash type PublicKey struct { - PublicKey crypt.PublicKey - PublicKeyHash crypt.PublicKeyHash - VaultName string - ID string - Policy *PublicKeyPolicy - Active bool + vault.KeyReference + Hash crypt.PublicKeyHash + Policy *PublicKeyPolicy + Active bool } // Signatory is a struct coordinate signatory action and select vault according to the key being used @@ -98,10 +96,9 @@ type SignRequest struct { } type keyVaultPair struct { - pkh crypt.PublicKeyHash - key vault.StoredKey - vault vault.Vault - name string + pkh crypt.PublicKeyHash + key vault.KeyReference + vaultName string } type keyCache struct { @@ -368,13 +365,7 @@ func (s *Signatory) Sign(ctx context.Context, req *SignRequest) (crypt.Signature return nil, err } - l = l.WithField(logVault, p.vault.Name()) - if n, ok := p.vault.(vault.VaultNamer); ok { - l = l.WithField(logVaultName, n.VaultName()) - } else { - l = l.WithField(logVaultName, p.name) - } - + l = l.WithField(logVault, p.key.Vault().Name()) if err = matchFilter(policy, req, msg); err != nil { l.Error(err) return nil, errors.Wrap(err, http.StatusForbidden) @@ -393,20 +384,21 @@ func (s *Signatory) Sign(ctx context.Context, req *SignRequest) (crypt.Signature } l.WithField(logRaw, hex.EncodeToString(req.Message)).Log(level, "About to sign raw bytes") digest := crypt.DigestFunc(req.Message) - signFunc := func(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { + + signFunc := func(ctx context.Context, message []byte, key vault.KeyReference) (crypt.Signature, error) { if err = s.config.Watermark.IsSafeToSign(ctx, req.PublicKeyHash, msg, &digest); err != nil { err = errors.Wrap(err, http.StatusConflict) l.Error(err) return nil, err } - return p.vault.SignMessage(ctx, message, key) + return key.Sign(ctx, message) } var sig crypt.Signature if s.config.Interceptor != nil { err = s.config.Interceptor(&SignInterceptorOptions{ Address: req.PublicKeyHash, - Vault: p.vault.Name(), + Vault: p.key.Vault().Name(), Req: msg.SignRequestKind(), Stat: opStat, }, func() (err error) { @@ -433,7 +425,7 @@ func (s *Signatory) listPublicKeys(ctx context.Context) (ret publicKeys, list [] ret = make(publicKeys) for name, v := range s.vaults { var vaultKeys []*keyVaultPair - iter := v.ListPublicKeys(ctx) + iter := v.List(ctx) keys: for { key, err := iter.Next() @@ -448,7 +440,7 @@ func (s *Signatory) listPublicKeys(ctx context.Context) (ret publicKeys, list [] } } pkh := key.PublicKey().Hash() - p := &keyVaultPair{pkh: pkh, key: key, vault: v, name: name} + p := &keyVaultPair{pkh: pkh, key: key, vaultName: name} s.cache.push(p) ret.Insert(pkh, p) @@ -471,13 +463,10 @@ func (s *Signatory) ListPublicKeys(ctx context.Context) ([]*PublicKey, error) { ret := make([]*PublicKey, len(list)) for i, p := range list { - pk := p.key.PublicKey() ret[i] = &PublicKey{ - PublicKey: pk, - PublicKeyHash: p.pkh, - VaultName: p.vault.Name(), - ID: p.key.ID(), - Policy: s.fetchPolicyOrDefault(p.pkh), + KeyReference: p.key, + Hash: p.pkh, + Policy: s.fetchPolicyOrDefault(p.pkh), } ret[i].Active = ret[i].Policy != nil } @@ -511,12 +500,12 @@ func (s *Signatory) GetPublicKey(ctx context.Context, keyHash crypt.PublicKeyHas return nil, err } + pol := s.fetchPolicyOrDefault(keyHash) return &PublicKey{ - PublicKey: p.key.PublicKey(), - PublicKeyHash: keyHash, - VaultName: p.vault.Name(), - ID: p.key.ID(), - Policy: s.fetchPolicyOrDefault(keyHash), + KeyReference: p.key, + Hash: keyHash, + Policy: s.fetchPolicyOrDefault(keyHash), + Active: pol != nil, }, nil } diff --git a/pkg/signatory/signatory_test.go b/pkg/signatory/signatory_test.go index 05232e50..e30ee775 100644 --- a/pkg/signatory/signatory_test.go +++ b/pkg/signatory/signatory_test.go @@ -46,8 +46,8 @@ func TestImport(t *testing.T) { imported, err := s.Import(context.Background(), "mock", privateKey, nil, nil) require.NoError(t, err) - require.Equal(t, "edpkv45regue1bWtuHnCgLU8xWKLwa9qRqv4gimgJKro4LSc3C5VjV", imported.PublicKey.String()) - require.Equal(t, "tz1LggX2HUdvJ1tF4Fvv8fjsrzLeW4Jr9t2Q", imported.PublicKeyHash.String()) + require.Equal(t, "edpkv45regue1bWtuHnCgLU8xWKLwa9qRqv4gimgJKro4LSc3C5VjV", imported.PublicKey().String()) + require.Equal(t, "tz1LggX2HUdvJ1tF4Fvv8fjsrzLeW4Jr9t2Q", imported.Hash.String()) list, err := s.ListPublicKeys(context.Background()) require.NoError(t, err) @@ -393,12 +393,10 @@ func TestListPublicKeys(t *testing.T) { LogPayloads: true, }, expected: "Vault not reachable", - lpk: func(ctx context.Context) vault.StoredKeysIterator { - return &TestKeyIterator{ - nxt: func(idx int) (key vault.StoredKey, err error) { - return nil, fmt.Errorf("Vault not reachable") - }, - } + lpk: func(ctx context.Context) vault.KeyIterator { + return NewTestIterator(func(idx int) (key vault.KeyReference, err error) { + return nil, fmt.Errorf("Vault not reachable") + }) }, }, { @@ -408,12 +406,10 @@ func TestListPublicKeys(t *testing.T) { AllowedOps: []string{"endorsement", "seed_nonce_revelation", "activate_account", "ballot", "reveal", "transaction", "origination", "delegation"}, LogPayloads: true, }, - lpk: func(ctx context.Context) vault.StoredKeysIterator { - return &TestKeyIterator{ - nxt: func(idx int) (key vault.StoredKey, err error) { - return nil, vault.ErrDone - }, - } + lpk: func(ctx context.Context) vault.KeyIterator { + return NewTestIterator(func(idx int) (key vault.KeyReference, err error) { + return nil, vault.ErrDone + }) }, }, { @@ -423,17 +419,14 @@ func TestListPublicKeys(t *testing.T) { AllowedOps: []string{"endorsement", "seed_nonce_revelation", "activate_account", "ballot", "reveal", "transaction", "origination", "delegation"}, LogPayloads: true, }, - lpk: func(ctx context.Context) vault.StoredKeysIterator { - return &TestKeyIterator{ - idx: 0, - nxt: func(idx int) (key vault.StoredKey, err error) { - if idx == 0 { - return nil, vault.ErrKey - } else { - return nil, vault.ErrDone - } - }, - } + lpk: func(ctx context.Context) vault.KeyIterator { + return NewTestIterator(func(idx int) (key vault.KeyReference, err error) { + if idx == 0 { + return nil, vault.ErrKey + } else { + return nil, vault.ErrDone + } + }) }, }, } @@ -448,7 +441,7 @@ func TestListPublicKeys(t *testing.T) { Vaults: map[string]*config.VaultConfig{"test": {Driver: "test"}}, Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { - return NewTestVault(nil, c.lpk, nil, nil, "test"), nil + return NewTestVault(nil, c.lpk, nil, "test"), nil }), Policy: hashmap.NewPublicKeyHashMap([]hashmap.PublicKeyKV[*signatory.PublicKeyPolicy]{{Key: pk.Hash(), Val: &c.policy}}), } diff --git a/pkg/signatory/utils_test.go b/pkg/signatory/utils_test.go index b9fa9eda..7a11f3b4 100644 --- a/pkg/signatory/utils_test.go +++ b/pkg/signatory/utils_test.go @@ -5,50 +5,48 @@ package signatory_test import ( "context" - "github.com/ecadlabs/gotez/v2/crypt" "github.com/ecadlabs/signatory/pkg/vault" ) -type GetPublicKey func(ctx context.Context, id string) (vault.StoredKey, error) -type ListPublicKeys func(ctx context.Context) vault.StoredKeysIterator -type Sign func(ctx context.Context, digest []byte, key vault.StoredKey) (crypt.Signature, error) +type GetPublicKey func(ctx context.Context, id string) (vault.KeyReference, error) +type ListPublicKeys func(ctx context.Context) vault.KeyIterator type Name func() string -type Next func() (vault.StoredKey, error) +type Next func() (vault.KeyReference, error) type TestVault struct { - vaultname string - gp GetPublicKey - lp ListPublicKeys - si Sign - na Name + gp GetPublicKey + lp ListPublicKeys + na Name } -func NewTestVault(g GetPublicKey, l ListPublicKeys, s Sign, n Name, vn string) *TestVault { +func NewTestVault(g GetPublicKey, l ListPublicKeys, n Name, vn string) *TestVault { return &TestVault{ - vaultname: vn, - gp: g, - lp: l, - si: s, - na: n, + gp: g, + lp: l, + na: n, } } -type TestKeyIterator struct { +type testKeyIterator struct { idx int - nxt func(idx int) (key vault.StoredKey, err error) + nth func(idx int) (key vault.KeyReference, err error) } -func (it *TestKeyIterator) Next() (key vault.StoredKey, err error) { - it.idx += 1 - return it.nxt(it.idx - 1) +func NewTestIterator(nth func(idx int) (key vault.KeyReference, err error)) *testKeyIterator { + return &testKeyIterator{ + nth: nth, + } } -func (v *TestVault) GetPublicKey(ctx context.Context, id string) (vault.StoredKey, error) { - return v.gp(ctx, id) + +func (it *testKeyIterator) Next() (key vault.KeyReference, err error) { + key, err = it.nth(it.idx) + it.idx += 1 + return } -func (v *TestVault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { + +func (v *TestVault) List(ctx context.Context) vault.KeyIterator { return v.lp(ctx) } -func (v *TestVault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - return v.si(ctx, message, key) -} -func (v *TestVault) Name() string { return v.na() } + +func (v *TestVault) Name() string { return v.na() } +func (v *TestVault) Close(context.Context) error { return nil } diff --git a/pkg/vault/aws/awskms.go b/pkg/vault/aws/awskms.go index 7df844bd..4555f1ae 100644 --- a/pkg/vault/aws/awskms.go +++ b/pkg/vault/aws/awskms.go @@ -34,6 +34,7 @@ type Vault struct { type awsKMSKey struct { key *kms.GetPublicKeyOutput pub crypt.PublicKey + v *Vault } type awsKMSIterator struct { @@ -45,44 +46,30 @@ type awsKMSIterator struct { } // PublicKey returns encoded public key -func (c *awsKMSKey) PublicKey() crypt.PublicKey { - return c.pub -} - -// ID returnd a unique key ID -func (c *awsKMSKey) ID() string { - return *c.key.KeyId -} +func (k *awsKMSKey) PublicKey() crypt.PublicKey { return k.pub } +func (k *awsKMSKey) ID() string { return *k.key.KeyId } +func (k *awsKMSKey) Vault() vault.Vault { return k.v } -func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - pkresp, err := v.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ - KeyId: &keyID, +func (k *awsKMSKey) Sign(ctx context.Context, message []byte) (crypt.Signature, error) { + digest := crypt.DigestFunc(message) + sout, err := k.v.client.Sign(ctx, &kms.SignInput{ + KeyId: k.key.KeyId, + Message: digest[:], + MessageType: types.MessageTypeDigest, + SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, }) if err != nil { return nil, err } - if pkresp.KeyUsage != types.KeyUsageTypeSignVerify { - return nil, errors.New("key usage must be SIGN_VERIFY") - } - - pkixKey, err := cryptoutils.ParsePKIXPublicKey(pkresp.PublicKey) - if err != nil { - return nil, fmt.Errorf("failed to parse public key: %w", err) - } - - pub, err := crypt.NewPublicKeyFrom(pkixKey) + sig, err := crypt.NewSignatureFromBytes(sout.Signature, k.pub) if err != nil { - return nil, err + return nil, fmt.Errorf("(AWSKMS/%s): %w", *k.key.KeyId, err) } - - return &awsKMSKey{ - key: pkresp, - pub: pub, - }, nil + return sig, nil } -func (i *awsKMSIterator) Next() (key vault.StoredKey, err error) { +func (i *awsKMSIterator) Next() (key vault.KeyReference, err error) { for { if i.lko == nil || i.index == len(i.lko.Keys) { // get next page @@ -103,7 +90,7 @@ func (i *awsKMSIterator) Next() (key vault.StoredKey, err error) { } } - key, err = i.v.GetPublicKey(i.ctx, *i.lko.Keys[i.index].KeyId) + key, err = i.v.getPublicKey(i.ctx, i.lko.Keys[i.index].KeyId) i.index += 1 var kmserr smithy.APIError @@ -119,8 +106,37 @@ func (i *awsKMSIterator) Next() (key vault.StoredKey, err error) { } } -// ListPublicKeys returns a list of keys stored under the backend -func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +func (v *Vault) getPublicKey(ctx context.Context, keyID *string) (*awsKMSKey, error) { + pkresp, err := v.client.GetPublicKey(ctx, &kms.GetPublicKeyInput{ + KeyId: keyID, + }) + if err != nil { + return nil, err + } + + if pkresp.KeyUsage != types.KeyUsageTypeSignVerify { + return nil, errors.New("key usage must be SIGN_VERIFY") + } + + pkixKey, err := cryptoutils.ParsePKIXPublicKey(pkresp.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to parse public key: %w", err) + } + + pub, err := crypt.NewPublicKeyFrom(pkixKey) + if err != nil { + return nil, err + } + + return &awsKMSKey{ + key: pkresp, + pub: pub, + v: v, + }, nil +} + +// List returns a list of keys stored under the backend +func (v *Vault) List(ctx context.Context) vault.KeyIterator { return &awsKMSIterator{ ctx: ctx, v: v, @@ -133,25 +149,8 @@ func (v *Vault) Name() string { return "AWSKMS" } -func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - digest := crypt.DigestFunc(message) - kid := key.ID() - sout, err := v.client.Sign(ctx, &kms.SignInput{ - KeyId: &kid, - Message: digest[:], - MessageType: types.MessageTypeDigest, - SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256, - }) - if err != nil { - return nil, err - } - pubkey := key.(*awsKMSKey) - - sig, err := crypt.NewSignatureFromBytes(sout.Signature, pubkey.pub) - if err != nil { - return nil, fmt.Errorf("(AWSKMS/%s): %w", kid, err) - } - return sig, nil +func (v *Vault) Close(context.Context) error { + return nil } func NewConfig(ctx context.Context, config *Config) (aws.Config, error) { diff --git a/pkg/vault/azure/azure.go b/pkg/vault/azure/azure.go index 060036ed..1be259ba 100644 --- a/pkg/vault/azure/azure.go +++ b/pkg/vault/azure/azure.go @@ -58,10 +58,56 @@ type Vault struct { type azureKey struct { bundle *keyBundle pub *crypt.ECDSAPublicKey + v *Vault } -func (a *azureKey) PublicKey() crypt.PublicKey { return a.pub } -func (a *azureKey) ID() string { return a.bundle.Key.KeyID } +func (k *azureKey) PublicKey() crypt.PublicKey { return k.pub } +func (k *azureKey) ID() string { return k.bundle.Key.KeyID } +func (k *azureKey) Vault() vault.Vault { return k.v } + +func (key *azureKey) Sign(ctx context.Context, message []byte) (crypt.Signature, error) { + digest := crypt.DigestFunc(message) + var req signRequest + if req.Algorithm = algByCurve(key.pub.Curve); req.Algorithm == "" { + return nil, errors.Wrap(fmt.Errorf("(Azure/%s): can't find corresponding signature algorithm for %s curve", key.v.config.Vault, key.bundle.Key.Curve), http.StatusBadRequest) + } + req.Value = base64.RawURLEncoding.EncodeToString(digest[:]) + + u, err := key.v.makeURL(key.bundle.Key.KeyID, "/sign") + if err != nil { + return nil, fmt.Errorf("(Azure/%s): %w", key.v.config.Vault, err) + } + + r, err := json.Marshal(&req) + if err != nil { + return nil, fmt.Errorf("(Azure/%s): %w", key.v.config.Vault, err) + } + + var res keyOperationResult + status, err := key.v.request(ctx, key.v.client, "POST", u, bytes.NewReader(r), &res) + if err != nil { + err = fmt.Errorf("(Azure/%s): %w", key.v.config.Vault, err) + if status != 0 { + err = errors.Wrap(err, status) + } + return nil, err + } + + sig, err := base64.RawURLEncoding.DecodeString(res.Value) + if err != nil { + return nil, fmt.Errorf("(Azure/%s): %w", key.v.config.Vault, err) + } + + byteLen := (key.pub.Params().BitSize + 7) >> 3 + if len(sig) != byteLen*2 { + return nil, fmt.Errorf("(Azure/%s): invalid signature size %d", key.v.config.Vault, len(sig)) + } + return &crypt.ECDSASignature{ + R: new(big.Int).SetBytes(sig[:byteLen]), + S: new(big.Int).SetBytes(sig[byteLen:]), + Curve: key.pub.Curve, + }, nil +} // New creates new Azure KeyVault backend func New(ctx context.Context, config *Config) (vault *Vault, err error) { @@ -160,7 +206,7 @@ type azureIterator struct { done bool } -func (a *azureIterator) Next() (key vault.StoredKey, err error) { +func (a *azureIterator) Next() (key vault.KeyReference, err error) { if a.done { return nil, vault.ErrDone } @@ -233,6 +279,7 @@ func (a *azureIterator) Next() (key vault.StoredKey, err error) { return &azureKey{ bundle: &bundle, pub: pub, + v: a.v, }, nil } else { panic(fmt.Sprintf("unsupported key type: %T", p)) // unlikely @@ -243,111 +290,20 @@ func (a *azureIterator) Next() (key vault.StoredKey, err error) { } } -// ListPublicKeys returns a list of keys stored under the backend -func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List returns a list of keys stored under the backend +func (v *Vault) List(ctx context.Context) vault.KeyIterator { return &azureIterator{ ctx: ctx, v: v, } } -// GetPublicKey returns a public key by given ID -func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - u, err := v.makeURL(keyID, "") - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - - var bundle keyBundle - status, err := v.request(ctx, v.client, "GET", u, nil, &bundle) - if err != nil { - err = fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - if status != 0 { - err = errors.Wrap(err, status) - } - return nil, err - } - - p, err := bundle.Key.PublicKey() - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - pub, err := crypt.NewPublicKeyFrom(p) - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - if p, ok := pub.(*crypt.ECDSAPublicKey); ok { - return &azureKey{ - bundle: &bundle, - pub: p, - }, nil - } else { - panic(fmt.Sprintf("unsupported key type: %T", pub)) // unlikely - } -} - -// Name returns backend name func (v *Vault) Name() string { - return "Azure" -} - -// VaultName returns vault name -func (v *Vault) VaultName() string { - return v.config.Vault -} - -// Sign performs signing operation -func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - digest := crypt.DigestFunc(message) - azureKey, ok := key.(*azureKey) - if !ok { - return nil, errors.Wrap(fmt.Errorf("(Azure/%s): not a Azure key: %T", v.config.Vault, key), http.StatusBadRequest) - } - - var req signRequest - if req.Algorithm = algByCurve(azureKey.pub.Curve); req.Algorithm == "" { - return nil, errors.Wrap(fmt.Errorf("(Azure/%s): can't find corresponding signature algorithm for %s curve", v.config.Vault, azureKey.bundle.Key.Curve), http.StatusBadRequest) - } - req.Value = base64.RawURLEncoding.EncodeToString(digest[:]) - - u, err := v.makeURL(azureKey.bundle.Key.KeyID, "/sign") - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - - r, err := json.Marshal(&req) - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - - var res keyOperationResult - status, err := v.request(ctx, v.client, "POST", u, bytes.NewReader(r), &res) - if err != nil { - err = fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - if status != 0 { - err = errors.Wrap(err, status) - } - return nil, err - } - - sig, err := base64.RawURLEncoding.DecodeString(res.Value) - if err != nil { - return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) - } - - byteLen := (azureKey.pub.Params().BitSize + 7) >> 3 - if len(sig) != byteLen*2 { - return nil, fmt.Errorf("(Azure/%s): invalid signature size %d", v.config.Vault, len(sig)) - } - return &crypt.ECDSASignature{ - R: new(big.Int).SetBytes(sig[:byteLen]), - S: new(big.Int).SetBytes(sig[byteLen:]), - Curve: azureKey.pub.Curve, - }, nil + return fmt.Sprintf("Azure/%s", v.config.Vault) } // Import imports a private key -func (v *Vault) Import(ctx context.Context, priv crypt.PrivateKey, opt utils.Options) (vault.StoredKey, error) { +func (v *Vault) Import(ctx context.Context, priv crypt.PrivateKey, opt utils.Options) (vault.KeyReference, error) { keyName, ok, err := opt.GetString("name") if err != nil { return nil, fmt.Errorf("(Azure/%s): %w", v.config.Vault, err) @@ -404,6 +360,7 @@ func (v *Vault) Import(ctx context.Context, priv crypt.PrivateKey, opt utils.Opt return &azureKey{ bundle: &bundle, pub: p, + v: v, }, nil } else { panic(fmt.Sprintf("unsupported key type: %T", pub)) // unlikely @@ -448,6 +405,10 @@ func (v *Vault) Ready(ctx context.Context) (bool, error) { return true, nil } +func (v *Vault) Close(context.Context) error { + return nil +} + func algByCurve(curve elliptic.Curve) string { switch curve { case elliptic.P256(): diff --git a/pkg/vault/cloudkms/cloudkms.go b/pkg/vault/cloudkms/cloudkms.go index bd9d2882..2428e026 100644 --- a/pkg/vault/cloudkms/cloudkms.go +++ b/pkg/vault/cloudkms/cloudkms.go @@ -8,7 +8,6 @@ import ( "crypto/sha1" "encoding/pem" "fmt" - "net/http" kms "cloud.google.com/go/kms/apiv1" kmspb "cloud.google.com/go/kms/apiv1/kmspb" @@ -49,16 +48,34 @@ type Vault struct { type cloudKMSKey struct { key *kmspb.CryptoKeyVersion pub crypt.PublicKey + v *Vault } -// PublicKey returns encoded public key -func (c *cloudKMSKey) PublicKey() crypt.PublicKey { - return c.pub -} +func (k *cloudKMSKey) PublicKey() crypt.PublicKey { return k.pub } // PublicKey returns encoded public key +func (k *cloudKMSKey) ID() string { return k.key.Name } // ID returnd a unique key ID +func (k *cloudKMSKey) Vault() vault.Vault { return k.v } + +func (kmsKey *cloudKMSKey) Sign(ctx context.Context, message []byte) (crypt.Signature, error) { + digest := crypt.DigestFunc(message) + req := kmspb.AsymmetricSignRequest{ + Name: kmsKey.key.Name, + Digest: &kmspb.Digest{ + Digest: &kmspb.Digest_Sha256{ + Sha256: digest[:], + }, + }, + } + + resp, err := kmsKey.v.client.AsymmetricSign(ctx, &req) + if err != nil { + return nil, fmt.Errorf("(CloudKMS/%s) AsymmetricSign: %w", kmsKey.v.config.keyRingName(), err) + } -// ID returnd a unique key ID -func (c *cloudKMSKey) ID() string { - return c.key.Name + sig, err := crypt.NewSignatureFromBytes(resp.Signature, kmsKey.pub) + if err != nil { + return nil, fmt.Errorf("(CloudKMS/%s): %w", kmsKey.v.config.keyRingName(), err) + } + return sig, nil } func getAlgorithm(curve elliptic.Curve) kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm { @@ -90,7 +107,7 @@ type cloudKMSIterator struct { } // Next implements vault.StoredKeysIterator -func (c *cloudKMSIterator) Next() (vault.StoredKey, error) { +func (c *cloudKMSIterator) Next() (vault.KeyReference, error) { if c.keyIter == nil { return nil, vault.ErrDone } @@ -139,6 +156,7 @@ func (c *cloudKMSIterator) Next() (vault.StoredKey, error) { return &cloudKMSKey{ key: ver, pub: pub, + v: c.vault, }, nil } } @@ -146,8 +164,8 @@ func (c *cloudKMSIterator) Next() (vault.StoredKey, error) { } } -// ListPublicKeys returns a list of keys stored under the backend -func (c *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List returns a list of keys stored under the backend +func (c *Vault) List(ctx context.Context) vault.KeyIterator { return &cloudKMSIterator{ ctx: ctx, vault: c, @@ -155,61 +173,6 @@ func (c *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { } } -// GetPublicKey returns a public key by given ID -func (c *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - req := kmspb.GetCryptoKeyVersionRequest{ - Name: keyID, - } - - resp, err := c.client.GetCryptoKeyVersion(ctx, &req) - if err != nil { - return nil, fmt.Errorf("(CloudKMS/%s) GetCryptoKeyVersion: %w", c.config.keyRingName(), err) - } - - if resp.State != kmspb.CryptoKeyVersion_ENABLED { - return nil, fmt.Errorf("(CloudKMS/%s) key version is not enabled: %s", c.config.keyRingName(), keyID) - } - - ecKey, err := c.getPublicKey(ctx, resp.Name) - if err != nil { - return nil, fmt.Errorf("(CloudKMS/%s) getPublicKey: %w", c.config.keyRingName(), err) - } - - return &cloudKMSKey{ - key: resp, - pub: ecKey, - }, nil -} - -// Sign performs signing operation -func (c *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - digest := crypt.DigestFunc(message) - kmsKey, ok := key.(*cloudKMSKey) - if !ok { - return nil, errors.Wrap(fmt.Errorf("(CloudKMS/%s): not a CloudKMS key: %T ", c.config.keyRingName(), key), http.StatusBadRequest) - } - - req := kmspb.AsymmetricSignRequest{ - Name: kmsKey.key.Name, - Digest: &kmspb.Digest{ - Digest: &kmspb.Digest_Sha256{ - Sha256: digest[:], - }, - }, - } - - resp, err := c.client.AsymmetricSign(ctx, &req) - if err != nil { - return nil, fmt.Errorf("(CloudKMS/%s) AsymmetricSign: %w", c.config.keyRingName(), err) - } - - sig, err := crypt.NewSignatureFromBytes(resp.Signature, kmsKey.pub) - if err != nil { - return nil, fmt.Errorf("(CloudKMS/%s): %w", c.config.keyRingName(), err) - } - return sig, nil -} - // PKCS#11 CKM_RSA_AES_KEY_WRAP func wrapPrivateKey(pubKey *rsa.PublicKey, priv crypt.PrivateKey) ([]byte, error) { pkcs8Key, err := cryptoutils.MarshalPKCS8PrivateKey(priv) @@ -250,7 +213,7 @@ func wrapPrivateKey(pubKey *rsa.PublicKey, priv crypt.PrivateKey) ([]byte, error } // Import imports a private key -func (c *Vault) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.StoredKey, error) { +func (c *Vault) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.KeyReference, error) { keyName, ok, err := opt.GetString("name") if err != nil { return nil, fmt.Errorf("(CloudKMS/%s): %w", c.config.keyRingName(), err) @@ -362,18 +325,16 @@ func (c *Vault) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Optio return &cloudKMSKey{ key: ver, pub: (*crypt.ECDSAPublicKey)(&ecdsaKey.PublicKey), + v: c, }, nil } // Name returns backend name func (c *Vault) Name() string { - return "CloudKMS" + return fmt.Sprintf("CloudKMS/%s", c.config.keyRingName()) } -// VaultName returns vault name -func (c *Vault) VaultName() string { - return c.config.keyRingName() -} +func (c *Vault) Close(context.Context) error { return nil } // New creates new Google Cloud KMS backend func New(ctx context.Context, config *Config) (*Vault, error) { diff --git a/pkg/vault/hashicorp/vault.go b/pkg/vault/hashicorp/vault.go index 8ea03e3e..0a688f73 100644 --- a/pkg/vault/hashicorp/vault.go +++ b/pkg/vault/hashicorp/vault.go @@ -38,41 +38,25 @@ type Vault struct { type vaultKey struct { id string pub crypt.PublicKey + v *Vault } // PublicKey returns encoded public key -func (k *vaultKey) PublicKey() crypt.PublicKey { - return k.pub -} - -// ID returnd a unique key ID -func (k *vaultKey) ID() string { - return k.id -} - -type iterator struct { - ctx context.Context - v *Vault - keyList []string - index int -} - -func init() { - vault.RegisterVault("hashicorpvault", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) { - var conf Config - if node == nil || node.Kind == 0 { - return nil, errors.New("(HashicorpVault): config is missing") - } - if err := node.Decode(&conf); err != nil { - return nil, err - } - - if err := config.Validator().Struct(&conf); err != nil { - return nil, err - } +func (k *vaultKey) PublicKey() crypt.PublicKey { return k.pub } +func (k *vaultKey) ID() string { return k.id } +func (k *vaultKey) Vault() vault.Vault { return k.v } +func (k *vaultKey) Sign(ctx context.Context, message []byte) (crypt.Signature, error) { + digest := crypt.DigestFunc(message) + sout, err := k.v.Transit().Sign(k.id, digest[:], &SignOpts{Hash: "sha2-256", Preshashed: false}) + if err != nil { + return nil, err + } - return New(ctx, &conf) - }) + sig, err := crypt.NewSignatureFromBytes(sout, k.PublicKey()) + if err != nil { + return nil, err + } + return sig, nil } // New creates new Hashicorp Vault backend @@ -118,7 +102,7 @@ func New(ctx context.Context, cfg *Config) (*Vault, error) { // Name returns backend name func (v *Vault) Name() string { - return "HASHICORP_VAULT" + return "Hashicorp" } func (v *Vault) login() error { @@ -138,15 +122,22 @@ func (v *Vault) login() error { return nil } -// ListPublicKeys returns a list of keys stored under the backend -func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List returns a list of keys stored under the backend +func (v *Vault) List(ctx context.Context) vault.KeyIterator { return &iterator{ ctx: ctx, v: v, } } -func (i *iterator) Next() (key vault.StoredKey, err error) { +type iterator struct { + ctx context.Context + v *Vault + keyList []string + index int +} + +func (i *iterator) Next() (key vault.KeyReference, err error) { if i.keyList == nil { i.keyList, err = i.v.Transit().ListKeys() if err != nil { @@ -158,7 +149,7 @@ func (i *iterator) Next() (key vault.StoredKey, err error) { return nil, vault.ErrDone } - key, err = i.v.GetPublicKey(i.ctx, i.keyList[i.index]) + key, err = i.v.getPublicKey(i.ctx, i.keyList[i.index]) i.index += 1 if err != nil { @@ -168,7 +159,7 @@ func (i *iterator) Next() (key vault.StoredKey, err error) { return key, nil } -func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { +func (v *Vault) getPublicKey(ctx context.Context, keyID string) (*vaultKey, error) { wrappingPubKeyString, err := v.Transit().GetKeyWithContext(ctx, keyID) if err != nil { return nil, err @@ -195,20 +186,26 @@ func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey return &vaultKey{ id: keyID, pub: cryptPubKey, + v: v, }, nil } -func (v *Vault) SignMessage(ctx context.Context, message []byte, key vault.StoredKey) (crypt.Signature, error) { - digest := crypt.DigestFunc(message) +func (v *Vault) Close(context.Context) error { return nil } - sout, err := v.Transit().Sign(key.ID(), digest[:], &SignOpts{Hash: "sha2-256", Preshashed: false}) - if err != nil { - return nil, err - } +func init() { + vault.RegisterVault("hashicorpvault", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) { + var conf Config + if node == nil || node.Kind == 0 { + return nil, errors.New("(HashicorpVault): config is missing") + } + if err := node.Decode(&conf); err != nil { + return nil, err + } - sig, err := crypt.NewSignatureFromBytes(sout, key.PublicKey()) - if err != nil { - return nil, err - } - return sig, nil + if err := config.Validator().Struct(&conf); err != nil { + return nil, err + } + + return New(ctx, &conf) + }) } diff --git a/pkg/vault/ledger/vault.go b/pkg/vault/ledger/vault.go index 1e637158..b1a050b9 100644 --- a/pkg/vault/ledger/vault.go +++ b/pkg/vault/ledger/vault.go @@ -3,7 +3,6 @@ package ledger import ( "context" "fmt" - "net/http" "net/url" "strings" "time" @@ -66,10 +65,33 @@ type Config struct { type ledgerKey struct { id *keyID pub crypt.PublicKey + v *Vault } func (l *ledgerKey) PublicKey() crypt.PublicKey { return l.pub } func (l *ledgerKey) ID() string { return l.id.String() } +func (l *ledgerKey) Vault() vault.Vault { return l.v } + +func (key *ledgerKey) Sign(ctx context.Context, digest []byte) (crypt.Signature, error) { + res := make(chan crypt.Signature, 1) + errCh := make(chan error, 1) + + key.v.req <- &signReq{ + key: key.id, + data: digest, + sig: res, + err: errCh, + } + + select { + case pk := <-res: + return pk, nil + case err := <-errCh: + return nil, fmt.Errorf("(Ledger/%s): %w", key.v.config.ID, err) + case <-ctx.Done(): + return nil, ctx.Err() + } +} type ledgerIterator struct { ctx context.Context @@ -77,7 +99,7 @@ type ledgerIterator struct { idx int } -func (l *ledgerIterator) Next() (key vault.StoredKey, err error) { +func (l *ledgerIterator) Next() (key vault.KeyReference, err error) { if l.idx == len(l.v.keys) { return nil, vault.ErrDone } @@ -91,7 +113,7 @@ func (l *ledgerIterator) Next() (key vault.StoredKey, err error) { return pk, nil } -func (v *Vault) getPublicKey(ctx context.Context, id *keyID) (vault.StoredKey, error) { +func (v *Vault) getPublicKey(ctx context.Context, id *keyID) (*ledgerKey, error) { res := make(chan *ledgerKey, 1) errCh := make(chan error, 1) @@ -111,58 +133,20 @@ func (v *Vault) getPublicKey(ctx context.Context, id *keyID) (vault.StoredKey, e } } -// GetPublicKey returns a public key by given ID -func (v *Vault) GetPublicKey(ctx context.Context, id string) (vault.StoredKey, error) { - key, err := parseKeyID(id) - if err != nil { - return nil, errors.Wrap(fmt.Errorf("(Ledger/%s): %w", v.config.ID, err), http.StatusBadRequest) - } - return v.getPublicKey(ctx, key) -} - -// ListPublicKeys returns a list of keys stored under the backend -func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List returns a list of keys stored under the backend +func (v *Vault) List(ctx context.Context) vault.KeyIterator { return &ledgerIterator{ ctx: ctx, v: v, } } -func (v *Vault) SignMessage(ctx context.Context, digest []byte, key vault.StoredKey) (crypt.Signature, error) { - pk, ok := key.(*ledgerKey) - if !ok { - return nil, errors.Wrap(fmt.Errorf("(Ledger/%s): not a Ledger key: %T ", v.config.ID, key), http.StatusBadRequest) - } - - res := make(chan crypt.Signature, 1) - errCh := make(chan error, 1) - - v.req <- &signReq{ - key: pk.id, - data: digest, - sig: res, - err: errCh, - } - - select { - case pk := <-res: - return pk, nil - case err := <-errCh: - return nil, fmt.Errorf("(Ledger/%s): %w", v.config.ID, err) - case <-ctx.Done(): - return nil, ctx.Err() - } -} - // Name returns a backend name i.e. Ledger func (v *Vault) Name() string { - return "Ledger" + return fmt.Sprintf("Ledger/%s", v.config.ID) } -// VaultName returns an instance ID -func (v *Vault) VaultName() string { - return v.config.ID -} +func (v *Vault) Close(context.Context) error { return nil } func (v *Vault) worker() { var ( @@ -218,6 +202,7 @@ func (v *Vault) worker() { r.res <- &ledgerKey{ pub: pub, id: r.key, + v: v, } case *signReq: diff --git a/pkg/vault/memory/vault.go b/pkg/vault/memory/vault.go index 4a0d25bc..d3cb9110 100644 --- a/pkg/vault/memory/vault.go +++ b/pkg/vault/memory/vault.go @@ -4,72 +4,48 @@ package memory import ( "context" "fmt" - "net/http" "sync" "github.com/ecadlabs/gotez/v2/b58" "github.com/ecadlabs/gotez/v2/crypt" - "github.com/ecadlabs/signatory/pkg/errors" "github.com/ecadlabs/signatory/pkg/utils" "github.com/ecadlabs/signatory/pkg/vault" ) type PrivateKey struct { - PrivateKey crypt.PrivateKey - KeyID string + Key crypt.PrivateKey + ID 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() +type UnparsedKey struct { + Data string + ID string } -// ID get the id of this file key -func (f *PrivateKey) ID() string { - return f.KeyID +type keyRef struct { + *PrivateKey + v *Vault } -type UnparsedKey struct { - Data string - ID string +func (k *keyRef) PublicKey() crypt.PublicKey { return k.Key.Public() } +func (k *keyRef) Vault() vault.Vault { return k.v } +func (k *keyRef) Sign(ctx context.Context, message []byte) (sig crypt.Signature, err error) { + signature, err := k.Key.Sign(message) + if err != nil { + return nil, fmt.Errorf("(%s): %w", k.v.name, err) + } + return signature, nil } // Vault is a file system based vault type Vault struct { raw []*UnparsedKey keys []*PrivateKey - index map[string]*PrivateKey mtx sync.Mutex name string unlocked bool } -type IteratorElem interface { - Elem() (key vault.StoredKey, err error) -} - -type Iterator[T IteratorElem] struct { - keys []T - idx int -} - -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, err = i.keys[i.idx].Elem() - i.idx++ - return key, err -} - // NewUnparsed create a new in-mempory vault from Tezos encoded data. Call Unlock before use func NewUnparsed(data []*UnparsedKey, name string) *Vault { if name == "" { @@ -82,74 +58,40 @@ func NewUnparsed(data []*UnparsedKey, name string) *Vault { } // New create a new in-mempory vault. Call Unlock before use -func New(src []*PrivateKey, name string) (*Vault, error) { +func New(keys []*PrivateKey, name string) (*Vault, error) { if name == "" { name = "Mem" } - - keys := make([]*PrivateKey, len(src)) - index := make(map[string]*PrivateKey, len(src)) - - for i, k := range src { - var key *PrivateKey - if k.KeyID != "" { - key = k - } else { - id := k.KeyID - if id == "" { - id = k.PrivateKey.Public().Hash().String() - } - key = &PrivateKey{ - PrivateKey: k.PrivateKey, - KeyID: id, - } - } - keys[i] = k - index[key.KeyID] = k - } - return &Vault{ name: name, keys: keys, - index: index, unlocked: true, }, nil } -// ListPublicKeys list all public key available on disk -func (v *Vault) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List list all public key available on disk +func (v *Vault) List(ctx context.Context) vault.KeyIterator { v.mtx.Lock() defer v.mtx.Unlock() - return &Iterator[*PrivateKey]{keys: v.keys} -} -// GetPublicKey retrieve a public key -func (v *Vault) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - v.mtx.Lock() - defer v.mtx.Unlock() - key, ok := v.index[keyID] - if !ok { - return nil, errors.Wrap(fmt.Errorf("(%s): key not found in vault: %s", v.name, keyID), http.StatusNotFound) - } - return key, nil + snap := v.keys + i := 0 + return vault.IteratorFunc(func() (key vault.KeyReference, err error) { + if i >= len(snap) { + return nil, vault.ErrDone + } + k := &keyRef{ + PrivateKey: snap[i], + v: v, + } + i++ + return k, nil + }) } // Name returns backend name func (v *Vault) Name() string { return v.name } -// Sign sign using the specified key -func (v *Vault) SignMessage(ctx context.Context, message []byte, k vault.StoredKey) (sig crypt.Signature, err error) { - key, ok := k.(*PrivateKey) - if !ok { - return nil, errors.Wrap(fmt.Errorf("(%s): invalid key type: %T ", v.name, k), http.StatusBadRequest) - } - signature, err := key.PrivateKey.Sign(message) - if err != nil { - return nil, fmt.Errorf("(%s): %w", v.name, err) - } - return signature, nil -} - // Unlock unlock all encrypted keys on disk func (v *Vault) Unlock(ctx context.Context) error { v.mtx.Lock() @@ -160,7 +102,6 @@ func (v *Vault) Unlock(ctx context.Context) error { v.mtx.Unlock() keys := make([]*PrivateKey, len(v.raw)) - index := make(map[string]*PrivateKey, len(v.raw)) for i, entry := range v.raw { name := entry.ID @@ -181,55 +122,50 @@ func (v *Vault) Unlock(ctx context.Context) error { return fmt.Errorf("(%s): %w", v.name, err) } - id := entry.ID - if id == "" { - id = priv.Public().Hash().String() - } key := PrivateKey{ - PrivateKey: priv, - KeyID: id, + Key: priv, + ID: entry.ID, } keys[i] = &key - index[key.KeyID] = &key } v.mtx.Lock() defer v.mtx.Unlock() v.keys = keys - v.index = index v.unlocked = true return nil } -func (v *Vault) ImportKey(ctx context.Context, priv crypt.PrivateKey, opt utils.Options) (vault.StoredKey, error) { - id, ok, err := opt.GetString("name") +func (v *Vault) ImportKey(ctx context.Context, priv crypt.PrivateKey, opt utils.Options) (vault.KeyReference, error) { + id, _, err := opt.GetString("name") if err != nil { return nil, fmt.Errorf("(%s): %w", v.name, err) } - if !ok || id == "" { - id = priv.Public().Hash().String() - } - key := PrivateKey{ - PrivateKey: priv, - KeyID: id, + key := &PrivateKey{ + Key: priv, + ID: id, } v.mtx.Lock() defer v.mtx.Unlock() - v.keys = append(v.keys, &key) - v.index[key.KeyID] = &key + v.keys = append(v.keys, key) - return &key, nil + return &keyRef{ + PrivateKey: key, + v: v, + }, nil } +func (v *Vault) Close(context.Context) error { return nil } + type Importer struct { *Vault } -func (i *Importer) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.StoredKey, error) { +func (i *Importer) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.KeyReference, error) { return i.ImportKey(ctx, pk, opt) } diff --git a/pkg/vault/pkcs11/pkcs11_test.go b/pkg/vault/pkcs11/pkcs11_test.go index 07b46094..a33b34c6 100644 --- a/pkg/vault/pkcs11/pkcs11_test.go +++ b/pkg/vault/pkcs11/pkcs11_test.go @@ -72,23 +72,16 @@ func TestPKCS11Vault(t *testing.T) { t.Fatal(string(out)) } - v, err := New(context.Background(), &Config{ + v, err := New(&Config{ LibraryPath: path, Pin: userPIN, - Label: keyLabel, }) require.NoError(t, err) - t.Cleanup(func() { v.Close() }) + t.Cleanup(func() { v.Close(context.Background()) }) - require.NoError(t, v.Unlock(context.Background())) - - keys, err := vault.Collect(v.ListPublicKeys(context.Background())) + keys, err := vault.Collect(v.List(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) + _, ok := keys[0].PublicKey().(*crypt.ECDSAPublicKey) + require.True(t, ok) } diff --git a/pkg/vault/pkcs11/vault.go b/pkg/vault/pkcs11/vault.go index 8b558fa5..f9c0a132 100644 --- a/pkg/vault/pkcs11/vault.go +++ b/pkg/vault/pkcs11/vault.go @@ -3,70 +3,76 @@ package pkcs11 import ( "context" "encoding/hex" - stderr "errors" "fmt" "net/http" "os" + "path" "strconv" "github.com/ecadlabs/go-pkcs11/pkcs11" + "github.com/ecadlabs/gotez/v2" "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 +const ( + envLibraryPath = "PKCS11_PATH" + envSlot = "PKCS11_SLOT" + envPin = "PKCS11_PIN" +) + +type KeyConfig struct { + Label string `yaml:"label"` + ID string `yaml:"id"` +} + +type KeyPair struct { + Private *KeyConfig `yaml:"private"` + ExtendedPrivate bool `yaml:"extended_private"` // Specific to AWS CloudHSM: set to true if the private key has a public key value as an attribute + Public *KeyConfig `yaml:"public"` + PublicValue gotez.PublicKey `yaml:"public_value"` } type Config struct { - LibraryPath string - Pin string - Slot *uint - Label string - ObjectID []byte + LibraryPath string `yaml:"library_path"` + Slot *uint `yaml:"slot"` // Find the first slot with initialized token if empty + Pin string `yaml:"pin"` + Keys []*KeyPair `yaml:"keys"` // Use all available keys if nil + PublicKeysSearchOptions *PublicKeysSearchOptions `yaml:"public_keys_search_options"` } -type iterElem struct { - obj *pkcs11.Object - v *PKCS11Vault +type PublicKeysSearchOptions struct { + MatchLabel bool `yaml:"match_label"` + MatchID bool `yaml:"match_id"` + ExtendedPrivate bool `yaml:"extended_private"` // Specific to AWS CloudHSM: set to true if the private key has a public key value as an attribute } -func (i *iterElem) Elem() (vault.StoredKey, error) { - pk, err := i.obj.PrivateKey() - if err != nil { - return nil, i.v.formatError(err) +func (o *PublicKeysSearchOptions) flags() (flags pkcs11.MatchFlags) { + if o.MatchLabel { + flags |= pkcs11.MatchLabel } - - 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 + if o.MatchID { + flags |= pkcs11.MatchID + } + if o.ExtendedPrivate { + flags |= pkcs11.ExtendedPrivate } + return } -type keyPair struct { - obj *pkcs11.Object - kp pkcs11.KeyPair +func (c *Config) searchOptions() *PublicKeysSearchOptions { + if c.PublicKeysSearchOptions != nil { + return c.PublicKeysSearchOptions + } + return &PublicKeysSearchOptions{MatchLabel: true, MatchID: true, ExtendedPrivate: true} } -func (p *keyPair) ID() string { - return fmt.Sprintf("%08x", p.obj.Handle()) +type keyPair struct { + kp pkcs11.KeyPair + v *PKCS11Vault } func (p *keyPair) PublicKey() crypt.PublicKey { @@ -77,219 +83,257 @@ func (p *keyPair) PublicKey() crypt.PublicKey { 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 - } - } +func (p *keyPair) Vault() vault.Vault { return p.v } - module, err := pkcs11.Open(config.LibraryPath) +func (kp *keyPair) Sign(ctx context.Context, msg []byte) (crypt.Signature, error) { + digest := crypt.DigestFunc(msg) + sig, err := kp.kp.Sign(nil, digest[:], nil) if err != nil { - return nil, fmt.Errorf("(PKCS#11/%s): %w", config.LibraryPath, err) + return nil, kp.v.formatError(err) } - log.Debug(module.Info()) - v := PKCS11Vault{ - module: module, - info: module.Info(), - conf: conf, + ret, err := crypt.NewSignatureFromBytes(sig, kp.PublicKey()) + if err != nil { + return nil, kp.v.formatError(err) } - return &v, nil + return ret, nil } -func (v *PKCS11Vault) Close() error { - if err := v.session.Close(); err != nil { - return err - } - return v.module.Close() +type PKCS11Vault struct { + mod *pkcs11.Module + session *pkcs11.Session + keys []pkcs11.KeyPair + conf *Config } -type errIterator struct { - err error +func (v *PKCS11Vault) formatError(err error) error { + return formatError(v.mod, err) } -func (e errIterator) Next() (vault.StoredKey, error) { - return nil, e.err +// GetPublicKey returns a public key by given ID +func (v *PKCS11Vault) List(ctx context.Context) vault.KeyIterator { + i := 0 + return vault.IteratorFunc(func() (key vault.KeyReference, err error) { + if i >= len(v.keys) { + return nil, vault.ErrDone + } + kp := &keyPair{ + kp: v.keys[i], + v: v, + } + i++ + return kp, nil + }) } -// 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"))} - } +func (c *PKCS11Vault) Name() string { + return fmt.Sprintf("PKCS#11/%s %v", c.mod.Info().Manufacturer, c.mod.Info().Version) +} - 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)) - } +func formatError(mod *pkcs11.Module, err error) error { + return fmt.Errorf("(PKCS#11/%s %v): %w", mod.Info().Manufacturer, mod.Info().Version, err) +} - objects, err := v.session.Objects(filter...) +func findSlot(mod *pkcs11.Module) (uint, error) { + // use first slot with a token + slots, err := mod.SlotIDs() if err != nil { - return errIterator{v.formatError(err)} + return 0, formatError(mod, err) } + for _, s := range slots { + si, err := mod.SlotInfo(s) + if err != nil { + return 0, formatError(mod, err) + } - elems := make([]*iterElem, len(objects)) - for i, o := range objects { - elems[i] = &iterElem{ - obj: o, - v: v, + if si.Token != nil && si.Token.Flags&pkcs11.TokenTokenInitialized != 0 { + return s, nil } } - return memory.NewIterator(elems) + return 0, formatError(mod, errors.New("token not found")) } -func (v *PKCS11Vault) GetPublicKey(ctx context.Context, id string) (vault.StoredKey, error) { - if v.session == nil { - return nil, v.formatError(errors.New("locked")) +func (v *PKCS11Vault) getKeyObject(conf *KeyConfig, class pkcs11.Class) (*pkcs11.Object, error) { + // find by label or id + filter := []pkcs11.Value{pkcs11.NewScalarV(pkcs11.AttributeClass, class)} + if conf.Label != "" { + filter = append(filter, pkcs11.NewString(pkcs11.AttributeLabel, conf.Label)) } - 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) + if conf.ID != "" { + id, err := hex.DecodeString(conf.ID) + if err != nil { + return nil, err } - return nil, v.formatError(err) + filter = append(filter, pkcs11.NewBytes(pkcs11.AttributeID, id)) } - pk, err := obj.PrivateKey() + + objects, err := v.session.Objects(filter...) if err != nil { return nil, v.formatError(err) } - kp, err := pk.KeyPair(pkcs11.MatchLabel | pkcs11.MatchID) - if err != nil { - return nil, v.formatError(err) + if len(objects) == 0 { + return nil, errors.Wrap(v.formatError(fmt.Errorf("key is not found: %#v", conf)), http.StatusNotFound) } - key := &keyPair{ - obj: obj, - kp: kp, + if len(objects) != 1 { + return nil, v.formatError(fmt.Errorf("non-unique key: %v", conf)) } - return key, nil + return objects[0], 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) initStatic() error { + for _, kpConf := range v.conf.Keys { + privObj, err := v.getKeyObject(kpConf.Private, pkcs11.ClassPrivateKey) + if err != nil { + return err + } -func (v *PKCS11Vault) Unlock(ctx context.Context) error { - if v.conf.Slot == nil { - // use first slot with a token - slots, err := v.module.SlotIDs() + priv, err := privObj.PrivateKey() if err != nil { return v.formatError(err) } - for _, s := range slots { - si, err := v.module.SlotInfo(s) + + var kp pkcs11.KeyPair + if kpConf.PublicValue != nil { + p, err := crypt.NewPublicKey(kpConf.PublicValue) if err != nil { return v.formatError(err) } - if si.Token != nil { - v.conf.Slot = &s - break + kp, err = priv.AddPublic(p.Unwrap()) + if err != nil { + return v.formatError(err) } + log.WithField("handle", fmt.Sprintf("%#016x", priv.Handle())).Debug("Private key object") + } else if kpConf.ExtendedPrivate { + // CloudHSM case + var err error + kp, err = priv.KeyPair(pkcs11.ExtendedPrivate) + if err != nil { + return v.formatError(err) + } + log.WithField("handle", fmt.Sprintf("%#016x", priv.Handle())).Debug("Extended private key object") + } else { + pubConf := kpConf.Public + if pubConf == nil { + pubConf = kpConf.Private + } + + pubObj, err := v.getKeyObject(pubConf, pkcs11.ClassPublicKey) + if err != nil { + return err + } + pub, err := pubObj.PublicKey() + if err != nil { + return v.formatError(err) + } + kp, err = priv.AddPublic(pub) + if err != nil { + return v.formatError(err) + } + log.WithFields(log.Fields{ + "private_handle": fmt.Sprintf("%#016x", priv.Handle()), + "public_handle": fmt.Sprintf("%#016x", pubObj.Handle()), + }).Debug("Key pair") } - if v.conf.Slot == nil { - return v.formatError(errors.New("Token not found")) - } + v.keys = append(v.keys, kp) } + return nil +} - session, err := v.module.NewSession(*v.conf.Slot, pkcs11.OptUserPIN(v.conf.Pin)) +func (v *PKCS11Vault) enumKeys() error { + filter := []pkcs11.Value{pkcs11.NewScalarV(pkcs11.AttributeClass, pkcs11.ClassPrivateKey)} + objects, err := v.session.Objects(filter...) if err != nil { return v.formatError(err) } - v.session = session + + searchOpt := v.conf.searchOptions() + for _, obj := range objects { + priv, err := obj.PrivateKey() + if err != nil { + log.WithField("handle", obj.Handle()).Error(err) + continue + } + kp, err := priv.KeyPair(searchOpt.flags()) + if err != nil { + log.WithField("handle", obj.Handle()).Error(err) + continue + } + log.WithFields(log.Fields{ + "private_handle": fmt.Sprintf("%#016x", priv.Handle()), + }).Debug("Key pair discovered") + v.keys = append(v.keys, kp) + } return nil } -func (c *PKCS11Vault) Name() string { - return "PKCS#11" +func (v *PKCS11Vault) initKeys() error { + if v.conf.Keys != nil { + return v.initStatic() + } + return v.enumKeys() } -func (c *PKCS11Vault) VaultName() string { - return fmt.Sprintf("%s %v", c.info.Manufacturer, c.info.Version) +func New(config *Config) (*PKCS11Vault, error) { + lib := config.LibraryPath + if lib == "" { + lib = os.Getenv(envLibraryPath) + } + + mod, err := pkcs11.Open(lib, pkcs11.OptOsLockingOk) + if err != nil { + return nil, fmt.Errorf("(PKCS#11/%s): %w", path.Base(lib), err) + } + log.Debug(mod.Info()) + + var slot uint + if config.Slot != nil { + slot = *config.Slot + } else if s := os.Getenv(envSlot); s != "" { + v, err := strconv.ParseUint(s, 0, strconv.IntSize) + if err != nil { + return nil, formatError(mod, err) + } + slot = uint(v) + } else if slot, err = findSlot(mod); err != nil { + return nil, err + } + + pin := config.Pin + if pin == "" { + pin = os.Getenv(envPin) + } + + session, err := mod.NewSession(slot, pkcs11.OptUserPIN(pin)) + if err != nil { + return nil, formatError(mod, err) + } + + v := PKCS11Vault{ + mod: mod, + session: session, + conf: config, + keys: make([]pkcs11.KeyPair, 0), + } + return &v, v.initKeys() } -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 (v *PKCS11Vault) Close(context.Context) error { + if err := v.session.Close(); err != nil { + return err + } + return v.mod.Close() } func init() { vault.RegisterVault("pkcs11", func(ctx context.Context, node *yaml.Node) (vault.Vault, error) { - var yamlConf yamlConfig + var conf Config if node == nil { return nil, errors.New("(PKCS#11): config is missing") } - if err := node.Decode(&yamlConf); err != nil { + if err := node.Decode(&conf); 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) + return New(&conf) }) } -var _ vault.VaultNamer = (*PKCS11Vault)(nil) +var _ vault.Vault = (*PKCS11Vault)(nil) diff --git a/pkg/vault/preamble/preamble.go b/pkg/vault/preamble/preamble.go new file mode 100644 index 00000000..7b34f175 --- /dev/null +++ b/pkg/vault/preamble/preamble.go @@ -0,0 +1,14 @@ +package preamble + +import ( + // Install all backends + _ "github.com/ecadlabs/signatory/pkg/vault/aws" + _ "github.com/ecadlabs/signatory/pkg/vault/azure" + _ "github.com/ecadlabs/signatory/pkg/vault/cloudkms" + _ "github.com/ecadlabs/signatory/pkg/vault/file" + _ "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/pkg/vault/vault.go b/pkg/vault/vault.go index f879e800..ae50b13b 100644 --- a/pkg/vault/vault.go +++ b/pkg/vault/vault.go @@ -11,29 +11,38 @@ import ( "gopkg.in/yaml.v3" ) -// StoredKey represents a public key which has a private counterpart stored on the backend side -type StoredKey interface { +// KeyReference represents a public key which has a private counterpart stored on the backend side +type KeyReference interface { PublicKey() crypt.PublicKey - ID() string + Sign(ctx context.Context, message []byte) (crypt.Signature, error) + Vault() Vault } -// StoredKeysIterator is used to iterate over stored public keys -type StoredKeysIterator interface { - Next() (StoredKey, error) +type WithID interface { + KeyReference + ID() string // Additional backend specific ID that can be displayed alongside the public key } +// KeyIterator is used to iterate over stored public keys +type KeyIterator interface { + Next() (KeyReference, error) +} + +type IteratorFunc func() (key KeyReference, err error) + +func (i IteratorFunc) Next() (key KeyReference, err error) { return i() } + // Vault interface that represent a secure key store type Vault interface { - GetPublicKey(ctx context.Context, id string) (StoredKey, error) - ListPublicKeys(ctx context.Context) StoredKeysIterator - SignMessage(ctx context.Context, msg []byte, key StoredKey) (crypt.Signature, error) + List(ctx context.Context) KeyIterator + Close(ctx context.Context) error Name() string } // Importer interface representing an importer backend type Importer interface { Vault - Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (StoredKey, error) + Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (KeyReference, error) } // Unlocker interface representing an unlocker backend @@ -42,11 +51,6 @@ type Unlocker interface { Unlock(ctx context.Context) error } -// VaultNamer might be implemented by some backends which can handle multiple vaults under single account -type VaultNamer interface { - VaultName() string -} - // ReadinessChecker is an optional interface implemented by a backend type ReadinessChecker interface { Ready(ctx context.Context) (bool, error) @@ -99,8 +103,8 @@ func Commands() []*cobra.Command { return commands } -func Collect(it StoredKeysIterator) ([]StoredKey, error) { - var keys []StoredKey +func Collect(it KeyIterator) ([]KeyReference, error) { + var keys []KeyReference keyLoop: for { key, err := it.Next() diff --git a/pkg/vault/yubi/yubi.go b/pkg/vault/yubi/yubi.go index f3d2d604..bfed9414 100644 --- a/pkg/vault/yubi/yubi.go +++ b/pkg/vault/yubi/yubi.go @@ -42,17 +42,28 @@ type Config struct { KeyImportDomains uint16 `yaml:"key_import_domains"` } -func (c *Config) id() string { - return fmt.Sprintf("%s/%d", c.Address, c.AuthKeyID) -} - type hsmKey struct { id uint16 pub crypt.PublicKey + hsm *HSM } func (h *hsmKey) PublicKey() crypt.PublicKey { return h.pub } func (h *hsmKey) ID() string { return fmt.Sprintf("%04x", h.id) } +func (h *hsmKey) Vault() vault.Vault { return h.hsm } + +// Sign performs signing operation +func (key *hsmKey) Sign(ctx context.Context, message []byte) (sig crypt.Signature, err error) { + digest := crypt.DigestFunc(message) + switch k := key.pub.(type) { + case *crypt.ECDSAPublicKey: + return key.hsm.signECDSA(digest[:], key.id, k.Curve) + case crypt.Ed25519PublicKey: + return key.hsm.signED25519(digest[:], key.id) + } + + return nil, fmt.Errorf("(YubiHSM/%s): unexpected key type: %T", key.hsm.conf.Address, key.pub) +} // HSM struct containing information required to interrogate a YubiHSM type HSM struct { @@ -62,12 +73,7 @@ type HSM struct { // Name returns backend name func (h *HSM) Name() string { - return "YubiHSM" -} - -// VaultName returns vault name -func (h *HSM) VaultName() string { - return h.conf.id() + return fmt.Sprintf("YubiHSM/%s", h.conf.Address) } type yubihsmStoredKeysIterator struct { @@ -134,11 +140,11 @@ func (h *HSM) listObjects(options ...commands.ListCommandOption) ([]commands.Obj } // Next implements vault.StoredKeysIterator -func (y *yubihsmStoredKeysIterator) Next() (key vault.StoredKey, err error) { +func (y *yubihsmStoredKeysIterator) Next() (key vault.KeyReference, err error) { if y.objects == nil { y.objects, err = y.hsm.listObjects(commands.NewObjectTypeOption(commands.ObjectTypeAsymmetricKey)) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", y.hsm.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", y.hsm.conf.Address, err) } } @@ -150,22 +156,22 @@ func (y *yubihsmStoredKeysIterator) Next() (key vault.StoredKey, err error) { obj := y.objects[y.idx] command, err := commands.CreateGetPubKeyCommand(obj.ObjectID) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", y.hsm.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", y.hsm.conf.Address, err) } res, err := y.hsm.session.SendEncryptedCommand(command) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", y.hsm.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", y.hsm.conf.Address, err) } pubKeyResponse, ok := res.(*commands.GetPubKeyResponse) if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", y.hsm.conf.id(), res) + return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", y.hsm.conf.Address, res) } y.idx++ pub, ok, err := parsePublicKey(pubKeyResponse) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", y.hsm.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", y.hsm.conf.Address, err) } if !ok { continue // Skip @@ -174,63 +180,29 @@ func (y *yubihsmStoredKeysIterator) Next() (key vault.StoredKey, err error) { return &hsmKey{ pub: pub, id: obj.ObjectID, + hsm: y.hsm, }, nil } } -// ListPublicKeys list all public key from connected Yubi HSM -func (h *HSM) ListPublicKeys(ctx context.Context) vault.StoredKeysIterator { +// List list all public key from connected Yubi HSM +func (h *HSM) List(ctx context.Context) vault.KeyIterator { return &yubihsmStoredKeysIterator{hsm: h} } -// GetPublicKey returns a public key by given ID -func (h *HSM) GetPublicKey(ctx context.Context, keyID string) (vault.StoredKey, error) { - id, err := strconv.ParseUint(keyID, 16, 16) - if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) - } - - command, err := commands.CreateGetPubKeyCommand(uint16(id)) - if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", h.conf.id(), err) - } - res, err := h.session.SendEncryptedCommand(command) - if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): GetPubKey: %w", h.conf.id(), err) - } - - pubKeyResponse, ok := res.(*commands.GetPubKeyResponse) - if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.id(), res) - } - - pub, ok, err := parsePublicKey(pubKeyResponse) - if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) - } - if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unsupported key type: %d", h.conf.id(), pubKeyResponse.Algorithm) - } - - return &hsmKey{ - pub: pub, - id: uint16(id), - }, nil -} - func (h *HSM) signECDSA(digest []byte, id uint16, curve elliptic.Curve) (*crypt.ECDSASignature, error) { command, err := commands.CreateSignDataEcdsaCommand(id, digest) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } res, err := h.session.SendEncryptedCommand(command) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): SignDataEcdsa: %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): SignDataEcdsa: %w", h.conf.Address, err) } ecdsaResponse, ok := res.(*commands.SignDataEcdsaResponse) if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.id(), res) + return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.Address, res) } var sig struct { @@ -238,7 +210,7 @@ func (h *HSM) signECDSA(digest []byte, id uint16, curve elliptic.Curve) (*crypt. S *big.Int } if _, err = asn1.Unmarshal(ecdsaResponse.Signature, &sig); err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } return &crypt.ECDSASignature{ R: sig.R, @@ -250,50 +222,32 @@ func (h *HSM) signECDSA(digest []byte, id uint16, curve elliptic.Curve) (*crypt. func (h *HSM) signED25519(digest []byte, id uint16) (crypt.Ed25519Signature, error) { command, err := commands.CreateSignDataEddsaCommand(id, digest) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } res, err := h.session.SendEncryptedCommand(command) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): SignDataEddsa: %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): SignDataEddsa: %w", h.conf.Address, err) } eddsaResponse, ok := res.(*commands.SignDataEddsaResponse) if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.id(), res) + return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.Address, res) } if len(eddsaResponse.Signature) != ed25519.SignatureSize { - return nil, fmt.Errorf("(YubiHSM/%s): invalid ED25519 signature length: %d", h.conf.id(), len(eddsaResponse.Signature)) + return nil, fmt.Errorf("(YubiHSM/%s): invalid ED25519 signature length: %d", h.conf.Address, len(eddsaResponse.Signature)) } return crypt.Ed25519Signature(eddsaResponse.Signature), nil } -// Sign performs signing operation -func (h *HSM) SignMessage(ctx context.Context, message []byte, k vault.StoredKey) (sig crypt.Signature, err error) { - digest := crypt.DigestFunc(message) - key, ok := k.(*hsmKey) - if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): not a YubiHSM key: %T", h.conf.id(), k) - } - - switch k := key.pub.(type) { - case *crypt.ECDSAPublicKey: - return h.signECDSA(digest[:], key.id, k.Curve) - case crypt.Ed25519PublicKey: - return h.signED25519(digest[:], key.id) - } - - return nil, fmt.Errorf("(YubiHSM/%s): unexpected key type: %T", h.conf.id(), key.pub) -} - var echoMessage = []byte("health") // Ready implements vault.ReadinessChecker func (h *HSM) Ready(ctx context.Context) (bool, error) { command, err := commands.CreateEchoCommand(echoMessage) if err != nil { - return false, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return false, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } res, err := h.session.SendEncryptedCommand(command) @@ -303,11 +257,11 @@ func (h *HSM) Ready(ctx context.Context) (bool, error) { echoResponse, ok := res.(*commands.EchoResponse) if !ok { - return false, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.id(), res) + return false, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.Address, res) } if !bytes.Equal(echoResponse.Data, echoMessage) { - return false, fmt.Errorf("(YubiHSM/%s): echoed data is invalid", h.conf.id()) + return false, fmt.Errorf("(YubiHSM/%s): echoed data is invalid", h.conf.Address) } return true, nil @@ -334,16 +288,16 @@ func getPrivateKeyData(pk crypt.PrivateKey) (typ string, alg commands.Algorithm, } // Import imports a private key -func (h *HSM) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.StoredKey, error) { +func (h *HSM) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options) (vault.KeyReference, error) { typ, alg, caps, p, err := getPrivateKeyData(pk) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } domains := h.conf.KeyImportDomains d, ok, err := opt.GetInt("domains") if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } if ok { domains = uint16(d) @@ -351,7 +305,7 @@ func (h *HSM) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options label, ok, err := opt.GetString("name") if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } if !ok { label = fmt.Sprintf("signatory-%s-%d", typ, time.Now().Unix()) @@ -359,25 +313,28 @@ func (h *HSM) Import(ctx context.Context, pk crypt.PrivateKey, opt utils.Options command, err := commands.CreatePutAsymmetricKeyCommand(0, []byte(label), domains, caps, alg, p, nil) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): %w", h.conf.Address, err) } res, err := h.session.SendEncryptedCommand(command) if err != nil { - return nil, fmt.Errorf("(YubiHSM/%s): PutAsymmetricKey: %w", h.conf.id(), err) + return nil, fmt.Errorf("(YubiHSM/%s): PutAsymmetricKey: %w", h.conf.Address, err) } keyResponse, ok := res.(*commands.PutAsymmetricKeyResponse) if !ok { - return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.id(), res) + return nil, fmt.Errorf("(YubiHSM/%s): unexpected response type: %T", h.conf.Address, res) } return &hsmKey{ id: keyResponse.KeyID, pub: pk.Public(), + hsm: h, }, nil } +func (h *HSM) Close(context.Context) error { return nil } + // New creates new YubiHSM backend func New(ctx context.Context, config *Config) (*HSM, error) { c := *config diff --git a/test/auth_test.go b/test/auth_test.go index 3a00d961..d8bd4a9b 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -76,7 +76,7 @@ func TestAuthenticatedRequest(t *testing.T) { Vaults: map[string]*config.VaultConfig{"mock": {Driver: "mock"}}, Watermark: watermark.Ignore{}, VaultFactory: vault.FactoryFunc(func(ctx context.Context, name string, conf *yaml.Node) (vault.Vault, error) { - return memory.New([]*memory.PrivateKey{{PrivateKey: signPriv}}, "Mock") + return memory.New([]*memory.PrivateKey{{Key: signPriv}}, "Mock") }), Policy: hashmap.NewPublicKeyHashMap([]hashmap.PublicKeyKV[*signatory.PublicKeyPolicy]{ {