Skip to content

Commit

Permalink
feat(crypto/keyring): add Linux's keyctl support (#21653)
Browse files Browse the repository at this point in the history
Signed-off-by: Alessio Treglia <al@essio.dev>
Co-authored-by: Alessio Treglia <alessio@jur.io>
Co-authored-by: Matt Kocubinski <mkocubinski@gmail.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
Co-authored-by: Marko <marko@baricevic.me>
  • Loading branch information
5 people authored Sep 16, 2024
1 parent 924798f commit c0eced8
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crypto/keyring/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 1 addition & 18 deletions crypto/keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 (
Expand Down
84 changes: 84 additions & 0 deletions crypto/keyring/keyring_linux.go
Original file line number Diff line number Diff line change
@@ -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"
51 changes: 51 additions & 0 deletions crypto/keyring/keyring_linux_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
})
}
}
35 changes: 35 additions & 0 deletions crypto/keyring/keyring_other.go
Original file line number Diff line number Diff line change
@@ -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...)
}

0 comments on commit c0eced8

Please sign in to comment.