From 52c63d4c442dc79e0eddf316c9a0b2456ab55309 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Thu, 24 Jun 2021 18:21:34 +0300 Subject: [PATCH] [#104] Support NEP-6 for authmate Drop neofs-crypto. Signed-off-by: Denis Kirillov --- README.md | 27 ++++++-- api/auth/center.go | 4 +- authmate/authmate.go | 45 +++++-------- cmd/authmate/main.go | 97 +++++++++++++++++++--------- cmd/s3-gw/app-settings.go | 1 + cmd/s3-gw/app.go | 54 ++-------------- creds/accessbox/accessbox.go | 42 ++++++------ creds/accessbox/bearer_token_test.go | 57 ++++++++-------- creds/tokens/credentials.go | 10 +-- go.mod | 1 - internal/wallet/wallet.go | 61 +++++++++++++++++ 11 files changed, 227 insertions(+), 172 deletions(-) create mode 100644 internal/wallet/wallet.go diff --git a/README.md b/README.md index c538354..45e368f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ default. To enable them use `--pprof` and `--metrics` flags or ## NeoFS AuthMate -Authmate is a tool to create gateway key pairs and AWS credentials. AWS users +Authmate is a tool to create gateway AWS credentials. AWS users are authenticated with access key IDs and secrets, while NeoFS users are authenticated with key pairs. To complicate things further we have S3 gateway that usually acts on behalf of some user, but user doesn't necessarily want to @@ -119,7 +119,7 @@ To solve this we use NeoFS bearer tokens that are signed by the owner (NeoFS "user") and that can implement any kind of policy for NeoFS requests allowed using this token. But tokens can't be used directly as AWS credentials, thus they're stored on NeoFS as regular objects and access key ID is just an -address of this object while secret is an SHA256 hash of this key. +address of this object while secret is generated randomly. Tokens are not stored on NeoFS in plaintext, they're encrypted with a set of gateway keys. So in order for gateway to be able to successfully extract bearer @@ -127,6 +127,17 @@ token the object needs to be stored in a container available for the gateway to read and it needs to be encrypted with this gateway's key (among others potentially). +### Variables +Authmate support the following variables to decrypt wallets provided by `--wallet` and `--gate-wallet` +parameters respectevely: +* `AUTHMATE_WALLET_PASSPHRASE` +* `AUTHMATE_WALLET_GATE_PASSPHRASE` + +If the passphrase is not specified, you will be asked to enter the password interactively: +``` +Enter password for wallet.json > +``` + #### Generation of wallet To generate wallets for gateways, run the following command: @@ -234,7 +245,7 @@ token will not be created. Example of a command to issue a secret with custom rules for multiple gates: ``` -$ ./neofs-authmate issue-secret --neofs-key user.key \ +$ ./neofs-authmate issue-secret --wallet wallet.json \ --peer 192.168.130.71:8080 \ --bearer-rules '{"records":[{"operation":"PUT","action":"ALLOW","filters":[],"targets":[{"role":"OTHERS","keys":[]}]}]}' \ --gate-public-key dd34f6dce9a4ce0990869ec6bd33a40e102a5798881cfe61d03a5659ceee1a64 \ @@ -242,6 +253,7 @@ $ ./neofs-authmate issue-secret --neofs-key user.key \ --create-session-token \ --session-rules '{"verb":"DELETE","wildcard":false,"containerID":{"value":"%CID"}}' +Enter password for wallet.json > { "access_key_id": "5g933dyLEkXbbAspouhPPTiyLZRg4axBW1axSPD87eVT_AiXsH4AjYy1iTJ4C1WExzjBrSobJsQFWEyKLREe5sQYM", "secret_access_key": "438bbd8243060e1e1c9dd4821756914a6e872ce29bf203b68f81b140ac91231c", @@ -255,14 +267,17 @@ any S3 client. #### Obtainment of a secret access key You can get a secret access key associated with access key ID by obtaining a -secret stored on the NeoFS network: +secret stored on the NeoFS network. Here example of providing one password (for `wallet.json`) via env variable +and other (for `gate-wallet.json`) interactively: ``` - $ ./neofs-authmate obtain-secret --neofs-key user.key \ + $ AUTHMATE_WALLET_PASSPHRASE=some-pwd \ + ./neofs-authmate obtain-secret --wallet wallet.json \ --peer 192.168.130.71:8080 \ - --gate-private-key b8ba980eb70b959be99915d2e0ad377809984ccd1dac0a6551907f81c2b33d21 \ + --gate-wallet gate-wallet.json \ --access-key-id 5g933dyLEkXbbAspouhPPTiyLZRg4axBW1axSPD87eVT_AiXsH4AjYy1iTJ4C1WExzjBrSobJsQFWEyKLREe5sQYM +Enter password for gate-wallet.json > { "secret_access_key": "438bbd8243060e1e1c9dd4821756914a6e872ce29bf203b68f81b140ac91231c" } diff --git a/api/auth/center.go b/api/auth/center.go index 72ca767..f909951 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -2,7 +2,6 @@ package auth import ( "context" - "crypto/ecdsa" "errors" "fmt" "io" @@ -13,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "github.com/nspcc-dev/neofs-s3-gw/creds/tokens" @@ -56,7 +56,7 @@ func (p prs) Seek(_ int64, _ int) (int64, error) { var _ io.ReadSeeker = prs(0) // New creates an instance of AuthCenter. -func New(conns pool.Pool, key *ecdsa.PrivateKey) Center { +func New(conns pool.Pool, key *keys.PrivateKey) Center { return ¢er{ cli: tokens.New(conns, key), reg: ®expSubmatcher{re: authorizationFieldRegexp}, diff --git a/authmate/authmate.go b/authmate/authmate.go index 5afeb34..940408b 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -12,6 +12,7 @@ import ( "time" "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/container" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" @@ -20,7 +21,6 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-node/pkg/policy" "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "github.com/nspcc-dev/neofs-s3-gw/creds/tokens" @@ -48,8 +48,8 @@ type ( IssueSecretOptions struct { ContainerID *cid.ID ContainerFriendlyName string - NeoFSKey *ecdsa.PrivateKey - GatesPublicKeys []*ecdsa.PublicKey + NeoFSKey *keys.PrivateKey + GatesPublicKeys []*keys.PublicKey EACLRules []byte ContextRules []byte SessionTkn bool @@ -58,7 +58,7 @@ type ( // ObtainSecretOptions contains options for passing to Agent.ObtainSecret method. ObtainSecretOptions struct { SecretAddress string - GatePrivateKey *ecdsa.PrivateKey + GatePrivateKey *keys.PrivateKey } ) @@ -127,7 +127,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr return err } - oid, err := ownerIDFromNeoFSKey(&options.NeoFSKey.PublicKey) + oid, err := ownerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) if err != nil { return err } @@ -155,7 +155,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr ir := &issuingResult{ AccessKeyID: accessKeyID, SecretAccessKey: secrets.AccessKey, - OwnerPrivateKey: hex.EncodeToString(crypto.MarshalPrivateKey(secrets.EphemeralKey)), + OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()), } enc := json.NewEncoder(w) @@ -257,7 +257,7 @@ func buildContext(rules []byte) (*session.ContainerContext, error) { return sessionCtx, nil } -func buildBearerToken(key *ecdsa.PrivateKey, table *eacl.Table, gateKey *ecdsa.PublicKey) (*token.BearerToken, error) { +func buildBearerToken(key *keys.PrivateKey, table *eacl.Table, gateKey *keys.PublicKey) (*token.BearerToken, error) { oid, err := ownerIDFromNeoFSKey(gateKey) if err != nil { return nil, err @@ -268,10 +268,10 @@ func buildBearerToken(key *ecdsa.PrivateKey, table *eacl.Table, gateKey *ecdsa.P bearerToken.SetOwner(oid) bearerToken.SetLifetime(math.MaxUint64, 0, 0) - return bearerToken, bearerToken.SignToken(key) + return bearerToken, bearerToken.SignToken(&key.PrivateKey) } -func buildBearerTokens(key *ecdsa.PrivateKey, table *eacl.Table, gatesKeys []*ecdsa.PublicKey) ([]*token.BearerToken, error) { +func buildBearerTokens(key *keys.PrivateKey, table *eacl.Table, gatesKeys []*keys.PublicKey) ([]*token.BearerToken, error) { bearerTokens := make([]*token.BearerToken, 0, len(gatesKeys)) for _, gateKey := range gatesKeys { tkn, err := buildBearerToken(key, table, gateKey) @@ -283,7 +283,7 @@ func buildBearerTokens(key *ecdsa.PrivateKey, table *eacl.Table, gatesKeys []*ec return bearerTokens, nil } -func buildSessionToken(key *ecdsa.PrivateKey, oid *owner.ID, ctx *session.ContainerContext, gateKey *ecdsa.PublicKey) (*session.Token, error) { +func buildSessionToken(key *keys.PrivateKey, oid *owner.ID, ctx *session.ContainerContext, gateKey *keys.PublicKey) (*session.Token, error) { tok := session.NewToken() tok.SetContext(ctx) uid, err := uuid.New().MarshalBinary() @@ -292,12 +292,12 @@ func buildSessionToken(key *ecdsa.PrivateKey, oid *owner.ID, ctx *session.Contai } tok.SetID(uid) tok.SetOwnerID(oid) - tok.SetSessionKey(crypto.MarshalPublicKey(gateKey)) + tok.SetSessionKey(gateKey.Bytes()) - return tok, tok.Sign(key) + return tok, tok.Sign(&key.PrivateKey) } -func buildSessionTokens(key *ecdsa.PrivateKey, oid *owner.ID, ctx *session.ContainerContext, gatesKeys []*ecdsa.PublicKey) ([]*session.Token, error) { +func buildSessionTokens(key *keys.PrivateKey, oid *owner.ID, ctx *session.ContainerContext, gatesKeys []*keys.PublicKey) ([]*session.Token, error) { sessionTokens := make([]*session.Token, 0, len(gatesKeys)) for _, gateKey := range gatesKeys { tkn, err := buildSessionToken(key, oid, ctx, gateKey) @@ -329,7 +329,7 @@ func createTokens(options *IssueSecretOptions, cid *cid.ID) ([]*accessbox.GateDa if err != nil { return nil, fmt.Errorf("failed to build context for session token: %w", err) } - oid, err := ownerIDFromNeoFSKey(&options.NeoFSKey.PublicKey) + oid, err := ownerIDFromNeoFSKey(options.NeoFSKey.PublicKey()) if err != nil { return nil, err } @@ -346,23 +346,10 @@ func createTokens(options *IssueSecretOptions, cid *cid.ID) ([]*accessbox.GateDa return gates, nil } -func ownerIDFromNeoFSKey(key *ecdsa.PublicKey) (*owner.ID, error) { - wallet, err := owner.NEO3WalletFromPublicKey(key) +func ownerIDFromNeoFSKey(key *keys.PublicKey) (*owner.ID, error) { + wallet, err := owner.NEO3WalletFromPublicKey((*ecdsa.PublicKey)(key)) if err != nil { return nil, err } return owner.NewIDFromNeo3Wallet(wallet), nil } - -// LoadPublicKey returns ecdsa.PublicKey from hex string. -func LoadPublicKey(val string) (*ecdsa.PublicKey, error) { - data, err := hex.DecodeString(val) - if err != nil { - return nil, fmt.Errorf("unknown key format (%q), expect: hex-string", val) - } - - if key := crypto.UnmarshalPublicKey(data); key != nil { - return key, nil - } - return nil, fmt.Errorf("couldn't unmarshal public key (%q)", val) -} diff --git a/cmd/authmate/main.go b/cmd/authmate/main.go index 7a6a7c9..7b9a3d0 100644 --- a/cmd/authmate/main.go +++ b/cmd/authmate/main.go @@ -10,11 +10,13 @@ import ( "syscall" "time" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-s3-gw/authmate" "github.com/nspcc-dev/neofs-s3-gw/internal/version" + "github.com/nspcc-dev/neofs-s3-gw/internal/wallet" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" + "github.com/spf13/viper" "github.com/urfave/cli/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -26,18 +28,25 @@ const ( ) var ( - neoFSKeyPathFlag string - peerAddressFlag string - eaclRulesFlag string - contextRulesFlag string - gatePrivateKeyFlag string - accessKeyIDFlag string - containerIDFlag string - containerFriendlyName string - gatesPublicKeysFlag cli.StringSlice - logEnabledFlag bool - logDebugEnabledFlag bool - sessionTokenFlag bool + walletPathFlag string + accountAddressFlag string + peerAddressFlag string + eaclRulesFlag string + contextRulesFlag string + gateWalletPathFlag string + gateAccountAddressFlag string + accessKeyIDFlag string + containerIDFlag string + containerFriendlyName string + gatesPublicKeysFlag cli.StringSlice + logEnabledFlag bool + logDebugEnabledFlag bool + sessionTokenFlag bool +) + +const ( + envWalletPassphrase = "wallet.passphrase" + envWalletGatePassphrase = "wallet.gate.passphrase" ) var zapConfig = zap.Config{ @@ -85,6 +94,11 @@ func main() { Commands: appCommands(), } + viper.AutomaticEnv() + viper.SetEnvPrefix("AUTHMATE") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AllowEmptyEnv(true) + if err := app.Run(os.Args); err != nil { _, _ = fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(100) @@ -119,11 +133,18 @@ func issueSecret() *cli.Command { Usage: "Issue a secret in NeoFS network", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "neofs-key", + Name: "wallet", Value: "", - Usage: "path to owner's neofs private ecdsa key", + Usage: "path to the wallet", Required: true, - Destination: &neoFSKeyPathFlag, + Destination: &walletPathFlag, + }, + &cli.StringFlag{ + Name: "address", + Value: "", + Usage: "address of wallet account", + Required: false, + Destination: &accountAddressFlag, }, &cli.StringFlag{ Name: "peer", @@ -174,7 +195,8 @@ func issueSecret() *cli.Command { Action: func(c *cli.Context) error { ctx, log := prepare() - key, err := crypto.LoadPrivateKey(neoFSKeyPathFlag) + password := wallet.GetPassword(viper.GetViper(), envWalletPassphrase) + key, err := wallet.GetKeyFromPath(walletPathFlag, accountAddressFlag, password) if err != nil { return cli.Exit(fmt.Sprintf("failed to load neofs private key: %s", err), 1) } @@ -182,7 +204,7 @@ func issueSecret() *cli.Command { ctx, cancel := context.WithCancel(ctx) defer cancel() - client, err := createSDKClient(ctx, log, key, peerAddressFlag) + client, err := createSDKClient(ctx, log, &key.PrivateKey, peerAddressFlag) if err != nil { return cli.Exit(fmt.Sprintf("failed to create sdk client: %s", err), 2) } @@ -196,9 +218,9 @@ func issueSecret() *cli.Command { } } - var gatesPublicKeys []*ecdsa.PublicKey + var gatesPublicKeys []*keys.PublicKey for _, key := range gatesPublicKeysFlag.Value() { - gpk, err := authmate.LoadPublicKey(key) + gpk, err := keys.NewPublicKeyFromString(key) if err != nil { return cli.Exit(fmt.Sprintf("failed to load gate's public key: %s", err), 5) } @@ -230,11 +252,18 @@ func obtainSecret() *cli.Command { Usage: "Obtain a secret from NeoFS network", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "neofs-key", + Name: "wallet", Value: "", - Usage: "path to owner's neofs private ecdsa key", + Usage: "path to the wallet", Required: true, - Destination: &neoFSKeyPathFlag, + Destination: &walletPathFlag, + }, + &cli.StringFlag{ + Name: "address", + Value: "", + Usage: "address of wallet account", + Required: false, + Destination: &accountAddressFlag, }, &cli.StringFlag{ Name: "peer", @@ -244,10 +273,18 @@ func obtainSecret() *cli.Command { Destination: &peerAddressFlag, }, &cli.StringFlag{ - Name: "gate-private-key", - Usage: "gate's private x25519 key", + Name: "gate-wallet", + Value: "", + Usage: "path to the wallet", Required: true, - Destination: &gatePrivateKeyFlag, + Destination: &gateWalletPathFlag, + }, + &cli.StringFlag{ + Name: "gate-address", + Value: "", + Usage: "address of wallet account", + Required: false, + Destination: &gateAccountAddressFlag, }, &cli.StringFlag{ Name: "access-key-id", @@ -259,7 +296,8 @@ func obtainSecret() *cli.Command { Action: func(c *cli.Context) error { ctx, log := prepare() - key, err := crypto.LoadPrivateKey(neoFSKeyPathFlag) + password := wallet.GetPassword(viper.GetViper(), envWalletPassphrase) + key, err := wallet.GetKeyFromPath(walletPathFlag, accountAddressFlag, password) if err != nil { return cli.Exit(fmt.Sprintf("failed to load neofs private key: %s", err), 1) } @@ -267,7 +305,7 @@ func obtainSecret() *cli.Command { ctx, cancel := context.WithCancel(ctx) defer cancel() - client, err := createSDKClient(ctx, log, key, peerAddressFlag) + client, err := createSDKClient(ctx, log, &key.PrivateKey, peerAddressFlag) if err != nil { return cli.Exit(fmt.Sprintf("failed to create sdk client: %s", err), 2) } @@ -276,7 +314,8 @@ func obtainSecret() *cli.Command { var _ = agent - gateCreds, err := crypto.LoadPrivateKey(gatePrivateKeyFlag) + password = wallet.GetPassword(viper.GetViper(), envWalletGatePassphrase) + gateCreds, err := wallet.GetKeyFromPath(gateWalletPathFlag, gateAccountAddressFlag, password) if err != nil { return cli.Exit(fmt.Sprintf("failed to create owner's private key: %s", err), 4) } diff --git a/cmd/s3-gw/app-settings.go b/cmd/s3-gw/app-settings.go index 990a01c..74cf2a5 100644 --- a/cmd/s3-gw/app-settings.go +++ b/cmd/s3-gw/app-settings.go @@ -151,6 +151,7 @@ func newSettings() *viper.Viper { v.SetEnvPrefix(envPrefix) v.SetConfigType("yaml") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.AllowEmptyEnv(true) // flags setup: flags := pflag.NewFlagSet("commandline", pflag.ExitOnError) diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index de687b6..a04066c 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -3,20 +3,16 @@ package main import ( "context" "encoding/hex" - "fmt" "math" "net" "net/http" - "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/auth" "github.com/nspcc-dev/neofs-s3-gw/api/handler" "github.com/nspcc-dev/neofs-s3-gw/api/layer" + "github.com/nspcc-dev/neofs-s3-gw/internal/wallet" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" "github.com/spf13/viper" "go.uber.org/zap" @@ -85,12 +81,8 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { reBalance = v } - var password *string - if v.IsSet(cfgWalletPassphrase) { - pwd := v.GetString(cfgWalletPassphrase) - password = &pwd - } - if key, err = getKeyFromWallet(v.GetString(cfgWallet), v.GetString(cfgAddress), password); err != nil { + password := wallet.GetPassword(v, cfgWalletPassphrase) + if key, err = wallet.GetKeyFromPath(v.GetString(cfgWallet), v.GetString(cfgAddress), password); err != nil { l.Fatal("could not load NeoFS private key", zap.Error(err)) } @@ -120,7 +112,7 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { obj = layer.NewLayer(l, conns) // prepare auth center - ctr = auth.New(conns, &key.PrivateKey) + ctr = auth.New(conns, key) if caller, err = handler.New(l, obj); err != nil { l.Fatal("could not initialize API handler", zap.Error(err)) @@ -142,44 +134,6 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { } } -func getKeyFromWallet(walletPath, addrStr string, password *string) (*keys.PrivateKey, error) { - if len(walletPath) == 0 { - return nil, fmt.Errorf("wallet path must not be empty") - } - w, err := wallet.NewWalletFromFile(walletPath) - if err != nil { - return nil, err - } - - var addr util.Uint160 - if len(addrStr) == 0 { - addr = w.GetChangeAddress() - } else { - addr, err = flags.ParseAddress(addrStr) - if err != nil { - return nil, fmt.Errorf("invalid address") - } - } - - acc := w.GetAccount(addr) - if acc == nil { - return nil, fmt.Errorf("couldn't find wallet account for %s", addrStr) - } - - if password == nil { - pwd, err := input.ReadPassword("Enter password > ") - if err != nil { - return nil, fmt.Errorf("couldn't read password") - } - password = &pwd - } - if err := acc.Decrypt(*password, w.Scrypt); err != nil { - return nil, fmt.Errorf("couldn't decrypt account: %w", err) - } - - return acc.PrivateKey(), nil -} - // Wait waits for application to finish. func (a *App) Wait() { a.log.Info("application started") diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go index d2126f7..231eb54 100644 --- a/creds/accessbox/accessbox.go +++ b/creds/accessbox/accessbox.go @@ -3,7 +3,6 @@ package accessbox import ( "bytes" "crypto/cipher" - "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -11,9 +10,9 @@ import ( "fmt" "io" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" - crypto "github.com/nspcc-dev/neofs-crypto" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/hkdf" "google.golang.org/protobuf/proto" @@ -24,18 +23,18 @@ type GateData struct { AccessKey string BearerToken *token.BearerToken SessionToken *session.Token - GateKey *ecdsa.PublicKey + GateKey *keys.PublicKey } // NewGateData returns GateData from provided bearer token and public gate key. -func NewGateData(gateKey *ecdsa.PublicKey, bearerTkn *token.BearerToken) *GateData { +func NewGateData(gateKey *keys.PublicKey, bearerTkn *token.BearerToken) *GateData { return &GateData{GateKey: gateKey, BearerToken: bearerTkn} } // Secrets represents AccessKey and key to encrypt gate tokens. type Secrets struct { AccessKey string - EphemeralKey *ecdsa.PrivateKey + EphemeralKey *keys.PrivateKey } // Marshal returns the wire-format of AccessBox. @@ -52,11 +51,11 @@ func (x *AccessBox) Unmarshal(data []byte) error { // Session token can be nil. func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) { box := &AccessBox{} - ephemeralKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + ephemeralKey, err := keys.NewPrivateKey() if err != nil { return nil, nil, err } - box.OwnerPublicKey = crypto.MarshalPublicKey(&ephemeralKey.PublicKey) + box.OwnerPublicKey = ephemeralKey.PublicKey().Bytes() secret, err := generateSecret() if err != nil { @@ -71,9 +70,12 @@ func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) { } // GetTokens returns gate tokens from AccessBox. -func (x *AccessBox) GetTokens(owner *ecdsa.PrivateKey) (*GateData, error) { - sender := crypto.UnmarshalPublicKey(x.OwnerPublicKey) - ownerKey := crypto.MarshalPublicKey(&owner.PublicKey) +func (x *AccessBox) GetTokens(owner *keys.PrivateKey) (*GateData, error) { + sender, err := keys.NewPublicKeyFromBytes(x.OwnerPublicKey, elliptic.P256()) + if err != nil { + return nil, fmt.Errorf("couldn't unmarshal OwnerPublicKey: %w", err) + } + ownerKey := owner.PublicKey().Bytes() for _, gate := range x.Gates { if !bytes.Equal(gate.GatePublicKey, ownerKey) { continue @@ -89,7 +91,7 @@ func (x *AccessBox) GetTokens(owner *ecdsa.PrivateKey) (*GateData, error) { return nil, fmt.Errorf("no gate data for key %x was found", ownerKey) } -func (x *AccessBox) addTokens(gatesData []*GateData, ephemeralKey *ecdsa.PrivateKey, secret []byte) error { +func (x *AccessBox) addTokens(gatesData []*GateData, ephemeralKey *keys.PrivateKey, secret []byte) error { for i, gate := range gatesData { encBearer, err := gate.BearerToken.Marshal() if err != nil { @@ -117,7 +119,7 @@ func (x *AccessBox) addTokens(gatesData []*GateData, ephemeralKey *ecdsa.Private return nil } -func encodeGate(ephemeralKey *ecdsa.PrivateKey, ownerKey *ecdsa.PublicKey, tokens *Tokens) (*AccessBox_Gate, error) { +func encodeGate(ephemeralKey *keys.PrivateKey, ownerKey *keys.PublicKey, tokens *Tokens) (*AccessBox_Gate, error) { data, err := proto.Marshal(tokens) if err != nil { return nil, err @@ -129,12 +131,12 @@ func encodeGate(ephemeralKey *ecdsa.PrivateKey, ownerKey *ecdsa.PublicKey, token } gate := new(AccessBox_Gate) - gate.GatePublicKey = crypto.MarshalPublicKey(ownerKey) + gate.GatePublicKey = ownerKey.Bytes() gate.Tokens = encrypted return gate, nil } -func decodeGate(gate *AccessBox_Gate, owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) (*GateData, error) { +func decodeGate(gate *AccessBox_Gate, owner *keys.PrivateKey, sender *keys.PublicKey) (*GateData, error) { data, err := decrypt(owner, sender, gate.Tokens) if err != nil { return nil, err @@ -153,14 +155,14 @@ func decodeGate(gate *AccessBox_Gate, owner *ecdsa.PrivateKey, sender *ecdsa.Pub return nil, err } - gateData := NewGateData(&owner.PublicKey, bearerTkn) + gateData := NewGateData(owner.PublicKey(), bearerTkn) gateData.SessionToken = sessionTkn gateData.AccessKey = hex.EncodeToString(tokens.AccessKey) return gateData, nil } -func generateShared256(prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) (sk []byte, err error) { - if prv.PublicKey.Curve != pub.Curve { +func generateShared256(prv *keys.PrivateKey, pub *keys.PublicKey) (sk []byte, err error) { + if prv.PublicKey().Curve != pub.Curve { return nil, fmt.Errorf("not equal curves") } @@ -183,7 +185,7 @@ func deriveKey(secret []byte) ([]byte, error) { return key, err } -func encrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) { +func encrypt(owner *keys.PrivateKey, sender *keys.PublicKey, data []byte) ([]byte, error) { enc, err := getCipher(owner, sender) if err != nil { return nil, err @@ -197,7 +199,7 @@ func encrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]b return enc.Seal(nonce, nonce, data, nil), nil } -func decrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) { +func decrypt(owner *keys.PrivateKey, sender *keys.PublicKey, data []byte) ([]byte, error) { dec, err := getCipher(owner, sender) if err != nil { return nil, err @@ -211,7 +213,7 @@ func decrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]b return dec.Open(nil, nonce, cypher, nil) } -func getCipher(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) (cipher.AEAD, error) { +func getCipher(owner *keys.PrivateKey, sender *keys.PublicKey) (cipher.AEAD, error) { secret, err := generateShared256(owner, sender) if err != nil { return nil, err diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go index d9263c7..32a90a1 100644 --- a/creds/accessbox/bearer_token_test.go +++ b/creds/accessbox/bearer_token_test.go @@ -1,16 +1,13 @@ package accessbox import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "testing" "github.com/google/uuid" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/stretchr/testify/require" ) @@ -19,22 +16,22 @@ func Test_tokens_encrypt_decrypt(t *testing.T) { tkn = token.NewBearerToken() tkn2 = token.NewBearerToken() ) - sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + sec, err := keys.NewPrivateKey() require.NoError(t, err) - cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + cred, err := keys.NewPrivateKey() require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) - require.NoError(t, tkn.SignToken(sec)) + require.NoError(t, tkn.SignToken(&sec.PrivateKey)) rawTkn, err := tkn.Marshal() require.NoError(t, err) - data, err := encrypt(cred, &cred.PublicKey, rawTkn) + data, err := encrypt(cred, cred.PublicKey(), rawTkn) require.NoError(t, err) - rawTkn2, err := decrypt(cred, &cred.PublicKey, data) + rawTkn2, err := decrypt(cred, cred.PublicKey(), data) require.NoError(t, err) err = tkn2.Unmarshal(rawTkn2) @@ -50,16 +47,16 @@ func Test_bearer_token_in_access_box(t *testing.T) { tkn = token.NewBearerToken() ) - sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + sec, err := keys.NewPrivateKey() require.NoError(t, err) - cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + cred, err := keys.NewPrivateKey() require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) - require.NoError(t, tkn.SignToken(sec)) + require.NoError(t, tkn.SignToken(&sec.PrivateKey)) - gate := NewGateData(&cred.PublicKey, tkn) + gate := NewGateData(cred.PublicKey(), tkn) box, _, err = PackTokens([]*GateData{gate}) require.NoError(t, err) @@ -82,10 +79,10 @@ func Test_session_token_in_access_box(t *testing.T) { tkn = session.NewToken() ) - sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + sec, err := keys.NewPrivateKey() require.NoError(t, err) - cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + cred, err := keys.NewPrivateKey() require.NoError(t, err) tok := session.NewToken() @@ -93,10 +90,10 @@ func Test_session_token_in_access_box(t *testing.T) { uid, err := uuid.New().MarshalBinary() require.NoError(t, err) tok.SetID(uid) - tok.SetSessionKey(crypto.MarshalPublicKey(&sec.PublicKey)) - require.NoError(t, tkn.Sign(sec)) + tok.SetSessionKey(sec.PublicKey().Bytes()) + require.NoError(t, tkn.Sign(&sec.PrivateKey)) - gate := NewGateData(&cred.PublicKey, token.NewBearerToken()) + gate := NewGateData(cred.PublicKey(), token.NewBearerToken()) gate.SessionToken = tkn box, _, err = PackTokens([]*GateData{gate}) require.NoError(t, err) @@ -119,29 +116,29 @@ func Test_accessbox_multiple_keys(t *testing.T) { tkn = token.NewBearerToken() ) - sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + sec, err := keys.NewPrivateKey() require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) - require.NoError(t, tkn.SignToken(sec)) + require.NoError(t, tkn.SignToken(&sec.PrivateKey)) count := 10 gates := make([]*GateData, 0, count) - keys := make([]*ecdsa.PrivateKey, 0, count) + privateKeys := make([]*keys.PrivateKey, 0, count) { // generate keys for i := 0; i < count; i++ { - cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + cred, err := keys.NewPrivateKey() require.NoError(t, err) - gates = append(gates, NewGateData(&cred.PublicKey, tkn)) - keys = append(keys, cred) + gates = append(gates, NewGateData(cred.PublicKey(), tkn)) + privateKeys = append(privateKeys, cred) } } box, _, err = PackTokens(gates) require.NoError(t, err) - for i, k := range keys { + for i, k := range privateKeys { tkns, err := box.GetTokens(k) require.NoError(t, err, "key #%d: %s failed", i, k) require.Equal(t, tkns.BearerToken, tkn) @@ -154,19 +151,19 @@ func Test_unknown_key(t *testing.T) { tkn = token.NewBearerToken() ) - sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + sec, err := keys.NewPrivateKey() require.NoError(t, err) - cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + cred, err := keys.NewPrivateKey() require.NoError(t, err) - wrongCred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + wrongCred, err := keys.NewPrivateKey() require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) - require.NoError(t, tkn.SignToken(sec)) + require.NoError(t, tkn.SignToken(&sec.PrivateKey)) - gate := NewGateData(&cred.PublicKey, tkn) + gate := NewGateData(cred.PublicKey(), tkn) box, _, err = PackTokens([]*GateData{gate}) require.NoError(t, err) diff --git a/creds/tokens/credentials.go b/creds/tokens/credentials.go index 4b4b7ee..841ad8d 100644 --- a/creds/tokens/credentials.go +++ b/creds/tokens/credentials.go @@ -3,12 +3,12 @@ package tokens import ( "bytes" "context" - "crypto/ecdsa" "errors" "strconv" "sync" "time" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/client" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/object" @@ -21,11 +21,11 @@ type ( // Credentials is a bearer token get/put interface. Credentials interface { GetTokens(context.Context, *object.Address) (*accessbox.GateData, error) - Put(context.Context, *cid.ID, *owner.ID, *accessbox.AccessBox, ...*ecdsa.PublicKey) (*object.Address, error) + Put(context.Context, *cid.ID, *owner.ID, *accessbox.AccessBox, ...*keys.PublicKey) (*object.Address, error) } cred struct { - key *ecdsa.PrivateKey + key *keys.PrivateKey pool pool.Pool } ) @@ -46,7 +46,7 @@ var bufferPool = sync.Pool{ var _ = New // New creates new Credentials instance using given cli and key. -func New(conns pool.Pool, key *ecdsa.PrivateKey) Credentials { +func New(conns pool.Pool, key *keys.PrivateKey) Credentials { return &cred{pool: conns, key: key} } @@ -91,7 +91,7 @@ func (c *cred) getAccessBox(ctx context.Context, address *object.Address) (*acce return &box, nil } -func (c *cred) Put(ctx context.Context, cid *cid.ID, issuer *owner.ID, box *accessbox.AccessBox, keys ...*ecdsa.PublicKey) (*object.Address, error) { +func (c *cred) Put(ctx context.Context, cid *cid.ID, issuer *owner.ID, box *accessbox.AccessBox, keys ...*keys.PublicKey) (*object.Address, error) { var ( err error created = strconv.FormatInt(time.Now().Unix(), 10) diff --git a/go.mod b/go.mod index eb21a19..6593b3e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/gorilla/mux v1.8.0 github.com/nspcc-dev/neo-go v0.95.3 github.com/nspcc-dev/neofs-api-go v1.27.1 - github.com/nspcc-dev/neofs-crypto v0.3.0 github.com/nspcc-dev/neofs-node v1.22.0 github.com/nspcc-dev/neofs-sdk-go v0.0.0-20210624072335-0348eb331c92 github.com/prometheus/client_golang v1.9.0 diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go new file mode 100644 index 0000000..a2c47c1 --- /dev/null +++ b/internal/wallet/wallet.go @@ -0,0 +1,61 @@ +package wallet + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/spf13/viper" +) + +// GetPassword gets passphrase for wallet. +func GetPassword(v *viper.Viper, variable string) *string { + var password *string + if v.IsSet(variable) { + pwd := v.GetString(variable) + password = &pwd + } + return password +} + +// GetKeyFromPath reads wallet and gets private key. +func GetKeyFromPath(walletPath, addrStr string, password *string) (*keys.PrivateKey, error) { + if len(walletPath) == 0 { + return nil, fmt.Errorf("wallet path must not be empty") + } + w, err := wallet.NewWalletFromFile(walletPath) + if err != nil { + return nil, err + } + + var addr util.Uint160 + if len(addrStr) == 0 { + addr = w.GetChangeAddress() + } else { + addr, err = flags.ParseAddress(addrStr) + if err != nil { + return nil, fmt.Errorf("invalid address") + } + } + + acc := w.GetAccount(addr) + if acc == nil { + return nil, fmt.Errorf("couldn't find wallet account for %s", addrStr) + } + + if password == nil { + pwd, err := input.ReadPassword(fmt.Sprintf("Enter password for %s > ", walletPath)) + if err != nil { + return nil, fmt.Errorf("couldn't read password") + } + password = &pwd + } + if err := acc.Decrypt(*password, w.Scrypt); err != nil { + return nil, fmt.Errorf("couldn't decrypt account: %w", err) + } + + return acc.PrivateKey(), nil +}