From c0eced8d7eb631e6ac2752a2ace129772c75736f Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Mon, 16 Sep 2024 16:35:11 +0800 Subject: [PATCH] feat(crypto/keyring): add Linux's keyctl support (#21653) Signed-off-by: Alessio Treglia Co-authored-by: Alessio Treglia Co-authored-by: Matt Kocubinski Co-authored-by: Julien Robert Co-authored-by: Marko --- CHANGELOG.md | 2 + crypto/keyring/doc.go | 2 + crypto/keyring/keyring.go | 19 +------ crypto/keyring/keyring_linux.go | 84 ++++++++++++++++++++++++++++ crypto/keyring/keyring_linux_test.go | 51 +++++++++++++++++ crypto/keyring/keyring_other.go | 35 ++++++++++++ 6 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 crypto/keyring/keyring_linux.go create mode 100644 crypto/keyring/keyring_linux_test.go create mode 100644 crypto/keyring/keyring_other.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e6e974aef66..021b7800dbd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Features * (baseapp) [#20291](https://github.com/cosmos/cosmos-sdk/pull/20291) Simulate nested messages. +* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Add a `bulk-add-genesis-account` genesis command to add many genesis accounts at once. +* (crypto/keyring) [#21653](https://github.com/cosmos/cosmos-sdk/pull/21653) New Linux-only backend that adds Linux kernel's `keyctl` support. * (runtime) [#21704](https://github.com/cosmos/cosmos-sdk/pull/21704) Add StoreLoader in simappv2. ### Improvements diff --git a/crypto/keyring/doc.go b/crypto/keyring/doc.go index a3bc8d8824ac..87a9e0908b36 100644 --- a/crypto/keyring/doc.go +++ b/crypto/keyring/doc.go @@ -33,6 +33,8 @@ // https://github.com/KDE/kwallet // pass This backend uses the pass command line utility to store and retrieve keys: // https://www.passwordstore.org/ +// keyctl This backend leverages the Linux's kernel security key management system +// to store cryptographic keys securely in memory. This is available on Linux only. // test This backend stores keys insecurely to disk. It does not prompt for a password to // be unlocked and it should be used only for testing purposes. // memory Same instance as returned by NewInMemory. This backend uses a transient storage. Keys diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index aee52a62563c..ef3b0a16df08 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -147,23 +147,6 @@ type Exporter interface { // Option overrides keyring configuration options. type Option func(options *Options) -// Options define the options of the Keyring. -type Options struct { - // supported signing algorithms for keyring - SupportedAlgos SigningAlgoList - // supported signing algorithms for Ledger - SupportedAlgosLedger SigningAlgoList - // define Ledger Derivation function - LedgerDerivation func() (ledger.SECP256K1, error) - // define Ledger key generation function - LedgerCreateKey func([]byte) types.PubKey - // define Ledger app name - LedgerAppName string - // indicate whether Ledger should skip DER Conversion on signature, - // depending on which format (DER or BER) the Ledger app returns signatures - LedgerSigSkipDERConv bool -} - // NewInMemory creates a transient keyring useful for testing // purposes and on-the-fly key generation. // Keybase options can be applied when generating this new Keybase. @@ -180,7 +163,7 @@ func NewInMemoryWithKeyring(kr keyring.Keyring, cdc codec.Codec, opts ...Option) // New creates a new instance of a keyring. // Keyring options can be applied when generating the new instance. // Available backends are "os", "file", "kwallet", "memory", "pass", "test". -func New( +func newKeyringGeneric( appName, backend, rootDir string, userInput io.Reader, cdc codec.Codec, opts ...Option, ) (Keyring, error) { var ( diff --git a/crypto/keyring/keyring_linux.go b/crypto/keyring/keyring_linux.go new file mode 100644 index 000000000000..7db47961bab1 --- /dev/null +++ b/crypto/keyring/keyring_linux.go @@ -0,0 +1,84 @@ +//go:build linux +// +build linux + +package keyring + +import ( + "fmt" + "io" + + "github.com/99designs/keyring" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/ledger" + "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// Linux-only backend options. +const BackendKeyctl = "keyctl" + +func KeyctlScopeUser(options *Options) { setKeyctlScope(options, "user") } +func KeyctlScopeUserSession(options *Options) { setKeyctlScope(options, "usersession") } +func KeyctlScopeSession(options *Options) { setKeyctlScope(options, "session") } +func KeyctlScopeProcess(options *Options) { setKeyctlScope(options, "process") } +func KeyctlScopeThread(options *Options) { setKeyctlScope(options, "thread") } + +// Options define the options of the Keyring. +type Options struct { + // supported signing algorithms for keyring + SupportedAlgos SigningAlgoList + // supported signing algorithms for Ledger + SupportedAlgosLedger SigningAlgoList + // define Ledger Derivation function + LedgerDerivation func() (ledger.SECP256K1, error) + // define Ledger key generation function + LedgerCreateKey func([]byte) types.PubKey + // define Ledger app name + LedgerAppName string + // indicate whether Ledger should skip DER Conversion on signature, + // depending on which format (DER or BER) the Ledger app returns signatures + LedgerSigSkipDERConv bool + // KeyctlScope defines the scope of the keyctl's keyring. + KeyctlScope string +} + +func newKeyctlBackendConfig(appName, _ string, _ io.Reader, opts ...Option) keyring.Config { + options := Options{ + KeyctlScope: keyctlDefaultScope, // currently "process" + } + + for _, optionFn := range opts { + optionFn(&options) + } + + return keyring.Config{ + AllowedBackends: []keyring.BackendType{keyring.KeyCtlBackend}, + ServiceName: appName, + KeyCtlScope: options.KeyctlScope, + } +} + +// New creates a new instance of a keyring. +// Keyring options can be applied when generating the new instance. +// Available backends are "os", "file", "kwallet", "memory", "pass", "test", "keyctl". +func New( + appName, backend, rootDir string, userInput io.Reader, cdc codec.Codec, opts ...Option, +) (Keyring, error) { + if backend != BackendKeyctl { + return newKeyringGeneric(appName, backend, rootDir, userInput, cdc, opts...) + } + + db, err := keyring.Open(newKeyctlBackendConfig(appName, "", userInput, opts...)) + if err != nil { + return nil, fmt.Errorf("couldn't open keyring for %q: %w", appName, err) + } + + return newKeystore(db, cdc, backend, opts...), nil +} + +func setKeyctlScope(options *Options, scope string) { options.KeyctlScope = scope } + +// this is private as it is meant to be here for SDK devs convenience +// as the user does not need to pick any default when he wants to +// initialize keyctl with the default scope. +const keyctlDefaultScope = "process" diff --git a/crypto/keyring/keyring_linux_test.go b/crypto/keyring/keyring_linux_test.go new file mode 100644 index 000000000000..a6695b6b9471 --- /dev/null +++ b/crypto/keyring/keyring_linux_test.go @@ -0,0 +1,51 @@ +//go:build linux +// +build linux + +package keyring + +import ( + "errors" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" +) + +func TestNewKeyctlKeyring(t *testing.T) { + cdc := getCodec() + + tests := []struct { + name string + appName string + backend string + dir string + userInput io.Reader + cdc codec.Codec + expectedErr error + }{ + { + name: "keyctl backend", + appName: "cosmos", + backend: BackendKeyctl, + dir: t.TempDir(), + userInput: strings.NewReader(""), + cdc: cdc, + expectedErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kr, err := New(tt.appName, tt.backend, tt.dir, tt.userInput, tt.cdc) + if tt.expectedErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Nil(t, kr) + require.True(t, errors.Is(err, tt.expectedErr)) + } + }) + } +} diff --git a/crypto/keyring/keyring_other.go b/crypto/keyring/keyring_other.go new file mode 100644 index 000000000000..9c25a1e954e2 --- /dev/null +++ b/crypto/keyring/keyring_other.go @@ -0,0 +1,35 @@ +//go:build !linux +// +build !linux + +package keyring + +import ( + "io" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/ledger" + "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// Options define the options of the Keyring. +type Options struct { + // supported signing algorithms for keyring + SupportedAlgos SigningAlgoList + // supported signing algorithms for Ledger + SupportedAlgosLedger SigningAlgoList + // define Ledger Derivation function + LedgerDerivation func() (ledger.SECP256K1, error) + // define Ledger key generation function + LedgerCreateKey func([]byte) types.PubKey + // define Ledger app name + LedgerAppName string + // indicate whether Ledger should skip DER Conversion on signature, + // depending on which format (DER or BER) the Ledger app returns signatures + LedgerSigSkipDERConv bool +} + +func New( + appName, backend, rootDir string, userInput io.Reader, cdc codec.Codec, opts ...Option, +) (Keyring, error) { + return newKeyringGeneric(appName, backend, rootDir, userInput, cdc, opts...) +}