diff --git a/README.md b/README.md index 7d2bfc8..4b099da 100644 --- a/README.md +++ b/README.md @@ -34,29 +34,22 @@ Minimalistic S3 gateway setup needs: NeoFS nodes with weighted load balancing). * a key used to communicate with NeoFS nodes Passed via `--neofs-key` parameter or `S3_GW_NEOFS-KEY` environment variable. - * a key used for client authentication - Passed via `--auth-key` parameter or `S3_GW_AUTH-KEY` environment variable. - To generate it use `neofs-authmate generate-keys` command. These two commands are functionally equivalent, they run the gate with one backend node, some keys and otherwise default settings: ``` -$ neofs-s3-gw -p 192.168.130.72:8080 --neofs-key KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr \ - --auth-key a04edd5b3c497eed83be25fb136bafd056928c17986440745775223615f2cbab +$ neofs-s3-gw -p 192.168.130.72:8080 --neofs-key KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr $ S3_GW_PEERS_0_ADDRESS=192.168.130.72:8080 \ S3_GW_NEOFS-KEY=KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr \ - S3_GW_AUTH-KEY=a04edd5b3c497eed83be25fb136bafd056928c17986440745775223615f2cbab \ neofs-s3-gw ``` It's also possible to specify uri scheme (grpc or grpcs) when using `-p` or environment variables: ``` -$ neofs-s3-gw -p grpc://192.168.130.72:8080 --neofs-key KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr \ - --auth-key a04edd5b3c497eed83be25fb136bafd056928c17986440745775223615f2cbab +$ neofs-s3-gw -p grpc://192.168.130.72:8080 --neofs-key KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr $ S3_GW_PEERS_0_ADDRESS=grpcs://192.168.130.72:8080 \ S3_GW_NEOFS-KEY=KxDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr \ - S3_GW_AUTH-KEY=a04edd5b3c497eed83be25fb136bafd056928c17986440745775223615f2cbab \ neofs-s3-gw ``` @@ -84,12 +77,10 @@ $ HTTP_GW_PEERS_0_ADDRESS=192.168.130.72:8080 HTTP_GW_PEERS_0_WEIGHT=9 \ This command will make gateway use 192.168.130.72 for 90% of requests and 192.168.130.71 for remaining 10%. -### Keys +### Key -NeoFS (`--neofs-key`) and authentication (`--auth-key`) keys are mandatory -parameters. NeoFS key can be a path to private key file (as raw bytes), a hex -string or (unencrypted) WIF string. Authentication key is either a path to -raw private key file or a hex string. +NeoFS (`--neofs-key`) is mandatory parameter. NeoFS key can be a path to private key file (as raw bytes), +a hex string or (unencrypted) WIF string. ### Binding and TLS @@ -208,7 +199,7 @@ potentially). #### Generation of key pairs -To generate key pairs for gateways, run the following command (`--count` is 1 +To generate neofs key pairs for gateways, run the following command (`--count` is 1 by default): ``` diff --git a/api/auth/center.go b/api/auth/center.go index 9bcdc70..86c0112 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -2,6 +2,7 @@ package auth import ( "context" + "crypto/ecdsa" "errors" "fmt" "io" @@ -15,7 +16,6 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/token" "github.com/nspcc-dev/neofs-s3-gw/authmate" - "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" "github.com/nspcc-dev/neofs-s3-gw/creds/tokens" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" "go.uber.org/zap" @@ -36,9 +36,8 @@ type ( // Params stores node connection parameters. Params struct { - Pool pool.Pool - Logger *zap.Logger - Credential hcs.Credentials + Pool pool.Pool + Logger *zap.Logger } prs int @@ -58,7 +57,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 hcs.PrivateKey) Center { +func New(conns pool.Pool, key *ecdsa.PrivateKey) Center { return ¢er{ cli: tokens.New(conns, key), reg: ®expSubmatcher{re: authorizationFieldRegexp}, diff --git a/authmate/authmate.go b/authmate/authmate.go index d7c65bf..5ede806 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -21,9 +21,9 @@ 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/hcs" "github.com/nspcc-dev/neofs-s3-gw/creds/tokens" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" "go.uber.org/zap" @@ -52,8 +52,7 @@ type ( ContainerID *cid.ID ContainerFriendlyName string NeoFSKey *ecdsa.PrivateKey - OwnerPrivateKey hcs.PrivateKey - GatesPublicKeys []hcs.PublicKey + GatesPublicKeys []*ecdsa.PublicKey EACLRules []byte ContextRules []byte SessionTkn bool @@ -62,7 +61,7 @@ type ( // ObtainSecretOptions contains options for passing to Agent.ObtainSecret method. ObtainSecretOptions struct { SecretAddress string - GatePrivateKey hcs.PrivateKey + GatePrivateKey *ecdsa.PrivateKey } ) @@ -134,7 +133,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr var ( err error cid *cid.ID - box accessbox.AccessBox + box *accessbox.AccessBox ) a.log.Info("check container", zap.Stringer("cid", options.ContainerID)) @@ -142,8 +141,6 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr return err } - box.SetOwnerPublicKey(options.OwnerPrivateKey.PublicKey()) - oid, err := ownerIDFromNeoFSKey(&options.NeoFSKey.PublicKey) if err != nil { return err @@ -155,35 +152,24 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr return fmt.Errorf("failed to build eacl table: %w", err) } - bearerTkn, err := buildBearerToken(options.NeoFSKey, oid, bearerRules) + bearerTkn, err := buildBearerToken(options.NeoFSKey, bearerRules, options.GatesPublicKeys[0]) if err != nil { return fmt.Errorf("failed to build bearer token: %w", err) } - err = box.AddBearerToken(bearerTkn, options.OwnerPrivateKey, options.GatesPublicKeys...) + sessionTkn, err := createSessionToken(options, oid) if err != nil { - return fmt.Errorf("failed to add bearer token to accessbox: %w", err) + return fmt.Errorf("failed to create session token: %w", err) } + + box, ownerKey, err := accessbox.PackTokens(bearerTkn, sessionTkn, options.GatesPublicKeys...) + if err != nil { + return err + } + a.log.Info("store bearer token into NeoFS", zap.Stringer("owner_tkn", bearerTkn.Issuer())) - if options.SessionTkn { - sessionRules, err := buildContext(options.ContextRules) - if err != nil { - return fmt.Errorf("failed to build context for session token: %w", err) - } - - sessionTkn, err := buildSessionToken(options.NeoFSKey, oid, sessionRules) - if err != nil { - return fmt.Errorf("failed to create session token: %w", err) - } - - err = box.AddSessionToken(sessionTkn, options.OwnerPrivateKey, options.GatesPublicKeys...) - if err != nil { - return fmt.Errorf("failed to add session token to accessbox: %w", err) - } - } - if !options.SessionTkn && len(options.ContextRules) > 0 { _, err := w.Write([]byte("Warning: rules for session token were set but --create-session flag wasn't, " + "so session token was not created\n")) @@ -193,8 +179,8 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr } address, err := tokens. - New(a.pool, options.OwnerPrivateKey). - Put(ctx, cid, oid, &box, options.GatesPublicKeys...) + New(a.pool, ownerKey). + Put(ctx, cid, oid, box, options.GatesPublicKeys...) if err != nil { return fmt.Errorf("failed to put bearer token: %w", err) } @@ -209,7 +195,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr ir := &issuingResult{ AccessKeyID: accessKeyID, SecretAccessKey: secret, - OwnerPrivateKey: options.OwnerPrivateKey.String(), + OwnerPrivateKey: hex.EncodeToString(crypto.MarshalPrivateKey(ownerKey)), } enc := json.NewEncoder(w) @@ -316,7 +302,12 @@ func buildContext(rules []byte) (*session.ContainerContext, error) { return sessionCtx, nil } -func buildBearerToken(key *ecdsa.PrivateKey, oid *owner.ID, table *eacl.Table) (*token.BearerToken, error) { +func buildBearerToken(key *ecdsa.PrivateKey, table *eacl.Table, ownerKey *ecdsa.PublicKey) (*token.BearerToken, error) { + oid, err := ownerIDFromNeoFSKey(ownerKey) + if err != nil { + return nil, err + } + bearerToken := token.NewBearerToken() bearerToken.SetEACLTable(table) bearerToken.SetOwner(oid) @@ -338,6 +329,17 @@ func buildSessionToken(key *ecdsa.PrivateKey, oid *owner.ID, ctx *session.Contai return tok, tok.Sign(key) } +func createSessionToken(options *IssueSecretOptions, oid *owner.ID) (*session.Token, error) { + if options.SessionTkn { + sessionRules, err := buildContext(options.ContextRules) + if err != nil { + return nil, fmt.Errorf("failed to build context for session token: %w", err) + } + return buildSessionToken(options.NeoFSKey, oid, sessionRules) + } + return nil, nil +} + // BearerToAccessKey returns secret access key generated from given BearerToken. func BearerToAccessKey(tkn *token.BearerToken) (string, error) { data, err := tkn.Marshal() @@ -356,3 +358,16 @@ func ownerIDFromNeoFSKey(key *ecdsa.PublicKey) (*owner.ID, error) { } 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 28b0d89..99a5591 100644 --- a/cmd/authmate/main.go +++ b/cmd/authmate/main.go @@ -3,7 +3,9 @@ package main import ( "context" "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" + "encoding/hex" "encoding/json" "fmt" "os" @@ -15,7 +17,6 @@ import ( 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/creds/hcs" "github.com/nspcc-dev/neofs-s3-gw/internal/version" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" "github.com/urfave/cli/v2" @@ -40,7 +41,6 @@ var ( contextRulesFlag string gatePrivateKeyFlag string accessKeyIDFlag string - ownerPrivateKeyFlag string containerIDFlag string containerFriendlyName string gatesPublicKeysFlag cli.StringSlice @@ -124,14 +124,14 @@ func appCommands() []*cli.Command { } } -func generateGatesKeys(count int) ([]hcs.Credentials, error) { +func generateGatesKeys(count int) ([]*ecdsa.PrivateKey, error) { var ( err error - res = make([]hcs.Credentials, count) + res = make([]*ecdsa.PrivateKey, count) ) for i := 0; i < count; i++ { - if res[i], err = hcs.Generate(rand.Reader); err != nil { + if res[i], err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil { return nil, err } } @@ -146,7 +146,7 @@ func generateKeys() *cli.Command { Flags: []cli.Flag{ &cli.IntFlag{ Name: "count", - Usage: "number of x25519 key pairs to generate", + Usage: "number of 256r1 key pairs to generate", Value: 1, Destination: &gatesKeysCountFlag, }, @@ -154,18 +154,18 @@ func generateKeys() *cli.Command { Action: func(c *cli.Context) error { _, log := prepare() - log.Info("start generating x25519 keys") + log.Info("start generating P-256 keys") csl, err := generateGatesKeys(gatesKeysCountFlag) if err != nil { return cli.Exit(fmt.Sprintf("failed to create key pairs of gates: %s", err), 1) } - log.Info("generated x25519 keys") + log.Info("generated P-256 keys") gatesKeys := make([]gateKey, len(csl)) for i, cs := range csl { - privateKey, publicKey := cs.PrivateKey().String(), cs.PublicKey().String() + privateKey, publicKey := hex.EncodeToString(cs.D.Bytes()), hex.EncodeToString(crypto.MarshalPublicKey(&cs.PublicKey)) gatesKeys[i] = gateKey{PrivateKey: privateKey, PublicKey: publicKey} } @@ -213,16 +213,10 @@ func issueSecret() *cli.Command { }, &cli.StringSliceFlag{ Name: "gate-public-key", - Usage: "public x25519 key of a gate (use flags repeatedly for multiple gates)", + Usage: "public 256r1 key of a gate (use flags repeatedly for multiple gates)", Required: true, Destination: &gatesPublicKeysFlag, }, - &cli.StringFlag{ - Name: "owner-private-key", - Usage: "owner's private x25519 key", - Required: false, - Destination: &ownerPrivateKeyFlag, - }, &cli.StringFlag{ Name: "container-id", Usage: "auth container id to put the secret into", @@ -269,14 +263,9 @@ func issueSecret() *cli.Command { } } - var owner hcs.Credentials - if owner, err = fetchHCSCredentials(ownerPrivateKeyFlag); err != nil { - return cli.Exit(fmt.Sprintf("failed to create owner's private key: %s", err), 4) - } - - var gatesPublicKeys []hcs.PublicKey + var gatesPublicKeys []*ecdsa.PublicKey for _, key := range gatesPublicKeysFlag.Value() { - gpk, err := hcs.LoadPublicKey(key) + gpk, err := authmate.LoadPublicKey(key) if err != nil { return cli.Exit(fmt.Sprintf("failed to load gate's public key: %s", err), 5) } @@ -287,7 +276,6 @@ func issueSecret() *cli.Command { ContainerID: containerID, ContainerFriendlyName: containerFriendlyName, NeoFSKey: key, - OwnerPrivateKey: owner.PrivateKey(), GatesPublicKeys: gatesPublicKeys, EACLRules: []byte(eaclRulesFlag), ContextRules: []byte(contextRulesFlag), @@ -355,7 +343,7 @@ func obtainSecret() *cli.Command { var _ = agent - gateCreds, err := hcs.NewCredentials(gatePrivateKeyFlag) + gateCreds, err := crypto.LoadPrivateKey(gatePrivateKeyFlag) if err != nil { return cli.Exit(fmt.Sprintf("failed to create owner's private key: %s", err), 4) } @@ -364,7 +352,7 @@ func obtainSecret() *cli.Command { obtainSecretOptions := &authmate.ObtainSecretOptions{ SecretAddress: secretAddress, - GatePrivateKey: gateCreds.PrivateKey(), + GatePrivateKey: gateCreds, } if err = agent.ObtainSecret(ctx, os.Stdout, obtainSecretOptions); err != nil { @@ -377,14 +365,6 @@ func obtainSecret() *cli.Command { return command } -func fetchHCSCredentials(val string) (hcs.Credentials, error) { - if val == "" { - return hcs.Generate(rand.Reader) - } - - return hcs.NewCredentials(val) -} - func createSDKClient(ctx context.Context, log *zap.Logger, key *ecdsa.PrivateKey, peerAddress string) (pool.Pool, error) { log.Debug("prepare connection pool") diff --git a/cmd/s3-gw/app-settings.go b/cmd/s3-gw/app-settings.go index 6a4b698..ed9c9d3 100644 --- a/cmd/s3-gw/app-settings.go +++ b/cmd/s3-gw/app-settings.go @@ -43,8 +43,7 @@ const ( // Settings. cfgLoggerSamplingThereafter = "logger.sampling.thereafter" // Keys. - cfgNeoFSPrivateKey = "neofs-key" - cfgGateAuthPrivateKey = "auth-key" + cfgNeoFSPrivateKey = "neofs-key" // HTTPS/TLS. cfgTLSKeyFile = "tls.key_file" @@ -163,7 +162,6 @@ func newSettings() *viper.Viper { versionFlag := flags.BoolP(cmdVersion, "v", false, "show version") flags.String(cfgNeoFSPrivateKey, "", "set value to hex string, WIF string, or path to NeoFS private key file") - flags.String(cfgGateAuthPrivateKey, "", "set path to file with auth (curve25519) private key to use in auth scheme") flags.Bool(cfgGRPCVerbose, false, "set debug mode of gRPC connections") flags.Duration(cfgRequestTimeout, defaultRequestTimeout, "set gRPC request timeout") diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index f5e848d..ab35eef 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -12,7 +12,6 @@ import ( "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/creds/hcs" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" "github.com/spf13/viper" "go.uber.org/zap" @@ -51,8 +50,6 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { ctr auth.Center obj layer.Client - hcsCred hcs.Credentials - poolPeers = fetchPeers(l, v) reBalance = defaultRebalanceTimer @@ -62,7 +59,6 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { maxClientsCount = defaultMaxClientsCount maxClientsDeadline = defaultMaxClientsDeadline - hcsCredential = v.GetString(cfgGateAuthPrivateKey) nfsCredential = v.GetString(cfgNeoFSPrivateKey) ) @@ -90,10 +86,6 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { l.Fatal("could not load NeoFS private key") } - if hcsCred, err = hcs.NewCredentials(hcsCredential); err != nil { - l.Fatal("could not load gate auth key") - } - if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) { tls = &tlsConfig{ KeyFile: v.GetString(cfgTLSKeyFile), @@ -102,7 +94,6 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { } l.Info("using credentials", - zap.String("HCS", hcsCredential), zap.String("NeoFS", nfsCredential)) opts := &pool.BuilderOptions{ @@ -121,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, hcsCred.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)) diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go index 0fc2b7d..f235e75 100644 --- a/creds/accessbox/accessbox.go +++ b/creds/accessbox/accessbox.go @@ -2,14 +2,19 @@ package accessbox import ( "bytes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" + "crypto/sha256" "fmt" + "io" "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" - "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + crypto "github.com/nspcc-dev/neofs-crypto" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/hkdf" "google.golang.org/protobuf/proto" ) @@ -28,43 +33,34 @@ func (x *AccessBox) Unmarshal(data []byte) error { return proto.Unmarshal(data, x) } -// AddBearerToken adds a bearer token to BearerTokens list. -func (x *AccessBox) AddBearerToken(tkn *token.BearerToken, owner hcs.PrivateKey, keys ...hcs.PublicKey) error { - if x.OwnerPublicKey == nil { - return fmt.Errorf("owner's public key is nil") +// PackTokens adds a bearer and session tokens to BearerTokens and SessionToken lists respectively. +// Session token can be nil. +func PackTokens(bearer *token.BearerToken, sess *session.Token, keys ...*ecdsa.PublicKey) (*AccessBox, *ecdsa.PrivateKey, error) { + box := &AccessBox{} + ephemeralKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err } - // restriction to rewrite token for the second time - if len(x.BearerTokens) > 0 { - return fmt.Errorf("bearer token is already set") - } - return x.addToken(tkn, &x.BearerTokens, owner, keys...) -} + box.OwnerPublicKey = crypto.MarshalPublicKey(&ephemeralKey.PublicKey) -// AddSessionToken adds a session token to SessionTokens list. -func (x *AccessBox) AddSessionToken(tkn *session.Token, owner hcs.PrivateKey, keys ...hcs.PublicKey) error { - if x.OwnerPublicKey == nil { - return fmt.Errorf("owner's public key is nil") + if err := box.addToken(bearer, &box.BearerTokens, ephemeralKey, keys...); err != nil { + return nil, nil, fmt.Errorf("failed to add bearer token to accessbox: %w", err) } - //restriction to rewrite token for the second time - if len(x.SessionTokens) > 0 { - return fmt.Errorf("bearer token is already set") + if sess != nil { + if err := box.addToken(sess, &box.SessionTokens, ephemeralKey, keys...); err != nil { + return nil, nil, fmt.Errorf("failed to add session token to accessbox: %w", err) + } } - return x.addToken(tkn, &x.SessionTokens, owner, keys...) -} -// SetOwnerPublicKey sets a public key of an issuer. -func (x *AccessBox) SetOwnerPublicKey(key hcs.PublicKey) { - x.OwnerPublicKey = key.Bytes() + return box, ephemeralKey, err } // GetBearerToken returns bearer token from AccessBox. -func (x *AccessBox) GetBearerToken(owner hcs.PrivateKey) (*token.BearerToken, error) { - sender, err := hcs.PublicKeyFromBytes(x.OwnerPublicKey) - if err != nil { - return nil, fmt.Errorf("failed to load owner public key from AccessBox: %w", err) - } +func (x *AccessBox) GetBearerToken(owner *ecdsa.PrivateKey) (*token.BearerToken, error) { + sender := crypto.UnmarshalPublicKey(x.OwnerPublicKey) + ownerKey := crypto.MarshalPublicKey(&owner.PublicKey) for _, data := range x.BearerTokens { - if !bytes.Equal(data.GatePublicKey, owner.PublicKey().Bytes()) { + if !bytes.Equal(data.GatePublicKey, ownerKey) { continue } tkn := token.NewBearerToken() @@ -74,17 +70,15 @@ func (x *AccessBox) GetBearerToken(owner hcs.PrivateKey) (*token.BearerToken, er return tkn, nil } - return nil, fmt.Errorf("no bearer token for key %s was found", owner.String()) + return nil, fmt.Errorf("no bearer token for key %x was found", ownerKey) } // GetSessionToken returns session token from AccessBox. -func (x *AccessBox) GetSessionToken(owner hcs.PrivateKey) (*session.Token, error) { - sender, err := hcs.PublicKeyFromBytes(x.OwnerPublicKey) - if err != nil { - return nil, fmt.Errorf("failed to load owner public key from AccessBox: %w", err) - } +func (x *AccessBox) GetSessionToken(owner *ecdsa.PrivateKey) (*session.Token, error) { + sender := crypto.UnmarshalPublicKey(x.OwnerPublicKey) + ownerKey := crypto.MarshalPublicKey(&owner.PublicKey) for _, data := range x.SessionTokens { - if !bytes.Equal(data.GatePublicKey, owner.PublicKey().Bytes()) { + if !bytes.Equal(data.GatePublicKey, ownerKey) { continue } tkn := session.NewToken() @@ -95,16 +89,16 @@ func (x *AccessBox) GetSessionToken(owner hcs.PrivateKey) (*session.Token, error return tkn, nil } - return nil, fmt.Errorf("no session token for key %s was found", owner.String()) + return nil, fmt.Errorf("no session token for key %x was found", ownerKey) } -func (x *AccessBox) addToken(tkn tokenInterface, list *[]*AccessBox_Token, owner hcs.PrivateKey, keys ...hcs.PublicKey) error { +func (x *AccessBox) addToken(tkn tokenInterface, list *[]*AccessBox_Token, owner *ecdsa.PrivateKey, keys ...*ecdsa.PublicKey) error { for i, sender := range keys { data, err := encodeToken(tkn, owner, sender) if err != nil { return fmt.Errorf("%w, sender = %d", err, i) } - *list = append(*list, newToken(data, sender.Bytes())) + *list = append(*list, newToken(data, crypto.MarshalPublicKey(sender))) } return nil } @@ -116,7 +110,7 @@ func newToken(data []byte, key []byte) *AccessBox_Token { return res } -func encodeToken(tkn tokenInterface, owner hcs.PrivateKey, sender hcs.PublicKey) ([]byte, error) { +func encodeToken(tkn tokenInterface, owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) ([]byte, error) { data, err := tkn.Marshal() if err != nil { return nil, err @@ -129,7 +123,7 @@ func encodeToken(tkn tokenInterface, owner hcs.PrivateKey, sender hcs.PublicKey) return encrypted, nil } -func decodeToken(data []byte, tkn tokenInterface, owner hcs.PrivateKey, sender hcs.PublicKey) error { +func decodeToken(data []byte, tkn tokenInterface, owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) error { decoded, err := decrypt(owner, sender, data) if err != nil { return err @@ -143,13 +137,32 @@ func decodeToken(data []byte, tkn tokenInterface, owner hcs.PrivateKey, sender h return nil } -func encrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, error) { - key, err := curve25519.X25519(owner.Bytes(), sender.Bytes()) - if err != nil { - return nil, err +func generateShared256(prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) (sk []byte, err error) { + if prv.PublicKey.Curve != pub.Curve { + return nil, fmt.Errorf("not equal curves") } - enc, err := chacha20poly1305.NewX(key) + x, _ := pub.Curve.ScalarMult(pub.X, pub.Y, prv.D.Bytes()) + if x == nil { + return nil, fmt.Errorf("shared key is point at infinity") + } + + sk = make([]byte, 32) + skBytes := x.Bytes() + copy(sk[len(sk)-len(skBytes):], skBytes) + return sk, nil +} + +func deriveKey(secret []byte) ([]byte, error) { + hash := sha256.New + kdf := hkdf.New(hash, secret, nil, nil) + key := make([]byte, 32) + _, err := io.ReadFull(kdf, key) + return key, err +} + +func encrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) { + enc, err := getCipher(owner, sender) if err != nil { return nil, err } @@ -162,15 +175,8 @@ func encrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, e return enc.Seal(nonce, nonce, data, nil), nil } -func decrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, error) { - sb := sender.Bytes() - - key, err := curve25519.X25519(owner.Bytes(), sb) - if err != nil { - return nil, err - } - - dec, err := chacha20poly1305.NewX(key) +func decrypt(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey, data []byte) ([]byte, error) { + dec, err := getCipher(owner, sender) if err != nil { return nil, err } @@ -182,3 +188,21 @@ func decrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, e nonce, cypher := data[:dec.NonceSize()], data[dec.NonceSize():] return dec.Open(nil, nonce, cypher, nil) } + +func getCipher(owner *ecdsa.PrivateKey, sender *ecdsa.PublicKey) (cipher.AEAD, error) { + secret, err := generateShared256(owner, sender) + if err != nil { + return nil, err + } + + key, err := deriveKey(secret) + if err != nil { + return nil, err + } + + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + return aead, nil +} diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go index 51f30b8..96c1b72 100644 --- a/creds/accessbox/bearer_token_test.go +++ b/creds/accessbox/bearer_token_test.go @@ -8,7 +8,6 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/token" - "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" "github.com/stretchr/testify/require" ) @@ -20,16 +19,16 @@ func Test_tokens_encode_decode(t *testing.T) { sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - cred, err := hcs.Generate(rand.Reader) + cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) require.NoError(t, tkn.SignToken(sec)) - data, err := encodeToken(tkn, cred.PrivateKey(), cred.PublicKey()) + data, err := encodeToken(tkn, cred, &cred.PublicKey) require.NoError(t, err) - err = decodeToken(data, tkn2, cred.PrivateKey(), cred.PublicKey()) + err = decodeToken(data, tkn2, cred, &cred.PublicKey) require.NoError(t, err) require.Equal(t, tkn, tkn2) @@ -37,22 +36,21 @@ func Test_tokens_encode_decode(t *testing.T) { func Test_bearer_token_in_access_box(t *testing.T) { var ( - box, box2 AccessBox - tkn = token.NewBearerToken() + box *AccessBox + box2 AccessBox + tkn = token.NewBearerToken() ) sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - cred, err := hcs.Generate(rand.Reader) + cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) require.NoError(t, tkn.SignToken(sec)) - box.SetOwnerPublicKey(cred.PublicKey()) - - err = box.AddBearerToken(tkn, cred.PrivateKey(), cred.PublicKey()) + box, _, err = PackTokens(tkn, nil, &cred.PublicKey) require.NoError(t, err) data, err := box.Marshal() @@ -61,7 +59,7 @@ func Test_bearer_token_in_access_box(t *testing.T) { err = box2.Unmarshal(data) require.NoError(t, err) - tkn2, err := box2.GetBearerToken(cred.PrivateKey()) + tkn2, err := box2.GetBearerToken(cred) require.NoError(t, err) require.Equal(t, tkn, tkn2) @@ -69,35 +67,30 @@ func Test_bearer_token_in_access_box(t *testing.T) { func Test_accessbox_multiple_keys(t *testing.T) { var ( - box AccessBox + box *AccessBox tkn = token.NewBearerToken() ) sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - cred, err := hcs.Generate(rand.Reader) - require.NoError(t, err) - tkn.SetEACLTable(eacl.NewTable()) require.NoError(t, tkn.SignToken(sec)) count := 10 - pubs := make([]hcs.PublicKey, 0, count) - keys := make([]hcs.PrivateKey, 0, count) + pubs := make([]*ecdsa.PublicKey, 0, count) + keys := make([]*ecdsa.PrivateKey, 0, count) { // generate keys for i := 0; i < count; i++ { - cred, err := hcs.Generate(rand.Reader) + cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - pubs = append(pubs, cred.PublicKey()) - keys = append(keys, cred.PrivateKey()) + pubs = append(pubs, &cred.PublicKey) + keys = append(keys, cred) } } - box.SetOwnerPublicKey(cred.PublicKey()) - - err = box.AddBearerToken(tkn, cred.PrivateKey(), pubs...) + box, _, err = PackTokens(tkn, nil, pubs...) require.NoError(t, err) for i, k := range keys { @@ -109,27 +102,25 @@ func Test_accessbox_multiple_keys(t *testing.T) { func Test_unknown_key(t *testing.T) { var ( - box AccessBox + box *AccessBox tkn = token.NewBearerToken() ) sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - cred, err := hcs.Generate(rand.Reader) + cred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - wrongCred, err := hcs.Generate(rand.Reader) + wrongCred, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) tkn.SetEACLTable(eacl.NewTable()) require.NoError(t, tkn.SignToken(sec)) - box.SetOwnerPublicKey(cred.PublicKey()) - - err = box.AddBearerToken(tkn, cred.PrivateKey(), cred.PublicKey()) + box, _, err = PackTokens(tkn, nil, &cred.PublicKey) require.NoError(t, err) - _, err = box.GetBearerToken(wrongCred.PrivateKey()) + _, err = box.GetBearerToken(wrongCred) require.Error(t, err) } diff --git a/creds/hcs/credentials.go b/creds/hcs/credentials.go deleted file mode 100644 index 5e3009f..0000000 --- a/creds/hcs/credentials.go +++ /dev/null @@ -1,90 +0,0 @@ -package hcs - -import ( - "errors" - "io" - - "golang.org/x/crypto/curve25519" -) - -type ( - // Credentials is an HCS interface (private/public key). - Credentials interface { - PublicKey() PublicKey - PrivateKey() PrivateKey - } - - keyer interface { - io.WriterTo - - Bytes() []byte - String() string - } - - // PublicKey is a public key wrapper providing useful methods. - PublicKey interface { - keyer - } - - // PrivateKey is private key wrapper providing useful methods. - PrivateKey interface { - keyer - - PublicKey() PublicKey - } - - credentials struct { - public PublicKey - secret PrivateKey - } - - public []byte - secret []byte -) - -// ErrEmptyCredentials is returned when no credentials are provided. -var ErrEmptyCredentials = errors.New("empty credentials") - -var _ = NewCredentials - -// Generate generates new key pair using given source of randomness. -func Generate(r io.Reader) (Credentials, error) { - buf := make([]byte, curve25519.ScalarSize) - - if _, err := r.Read(buf); err != nil { - return nil, err - } - - sk := secret(buf) - return &credentials{ - secret: &sk, - public: sk.PublicKey(), - }, nil -} - -// NewCredentials loads private key from the string given and returns Credentials wrapper. -func NewCredentials(val string) (Credentials, error) { - if val == "" { - return nil, ErrEmptyCredentials - } - - sk, err := loadPrivateKey(val) - if err != nil { - return nil, err - } - - return &credentials{ - secret: sk, - public: sk.PublicKey(), - }, nil -} - -// PublicKey returns public key. -func (c *credentials) PublicKey() PublicKey { - return c.public -} - -// PrivateKey returns private key. -func (c *credentials) PrivateKey() PrivateKey { - return c.secret -} diff --git a/creds/hcs/public.go b/creds/hcs/public.go deleted file mode 100644 index 2ef2c4f..0000000 --- a/creds/hcs/public.go +++ /dev/null @@ -1,66 +0,0 @@ -package hcs - -import ( - "encoding/hex" - "io" - "io/ioutil" - "os" - - "golang.org/x/crypto/curve25519" -) - -func (p *public) Bytes() []byte { - buf := make([]byte, curve25519.PointSize) - copy(buf, *p) - return buf -} - -func (p *public) String() string { - buf := p.Bytes() - return hex.EncodeToString(buf) -} - -func (p *public) WriteTo(w io.Writer) (int64, error) { - pb := p.Bytes() - pl, err := w.Write(pb) - return int64(pl), err -} - -// PublicKeyFromBytes reads a public key from given bytes. -func PublicKeyFromBytes(v []byte) (PublicKey, error) { - pub := public(v) - return &pub, nil -} - -func publicKeyFromString(val string) (PublicKey, error) { - v, err := hex.DecodeString(val) - if err != nil { - return nil, err - } - - return PublicKeyFromBytes(v) -} - -// NewPublicKeyFromReader reads new public key from given reader. -func NewPublicKeyFromReader(r io.Reader) (PublicKey, error) { - data := make([]byte, curve25519.PointSize) - if _, err := r.Read(data); err != nil { - return nil, err - } - - return PublicKeyFromBytes(data) -} - -// LoadPublicKey loads public key from given file or (serialized) string. -func LoadPublicKey(val string) (PublicKey, error) { - data, err := ioutil.ReadFile(val) - if err != nil { - if os.IsNotExist(err) { - return publicKeyFromString(val) - } - - return nil, err - } - - return PublicKeyFromBytes(data) -} diff --git a/creds/hcs/secret.go b/creds/hcs/secret.go deleted file mode 100644 index 1236c42..0000000 --- a/creds/hcs/secret.go +++ /dev/null @@ -1,60 +0,0 @@ -package hcs - -import ( - "encoding/hex" - "io" - "io/ioutil" - "os" - - "golang.org/x/crypto/curve25519" -) - -func (s *secret) Bytes() []byte { - buf := make([]byte, curve25519.ScalarSize) - copy(buf, *s) - return buf -} - -func (s *secret) String() string { - buf := s.Bytes() - return hex.EncodeToString(buf) -} - -func (s *secret) PublicKey() PublicKey { - sk := s.Bytes() - - pb, _ := curve25519.X25519(sk, curve25519.Basepoint) - pk := public(pb) - return &pk -} - -func (s *secret) WriteTo(w io.Writer) (int64, error) { - sb := s.Bytes() - sl, err := w.Write(sb) - return int64(sl), err -} - -func privateKeyFromBytes(val []byte) (PrivateKey, error) { - sk := secret(val) - return &sk, nil -} - -func privateKeyFromString(val string) (PrivateKey, error) { - data, err := hex.DecodeString(val) - if err != nil { - return nil, err - } - - return privateKeyFromBytes(data) -} - -func loadPrivateKey(val string) (PrivateKey, error) { - data, err := ioutil.ReadFile(val) - if os.IsNotExist(err) { - return privateKeyFromString(val) - } else if err != nil { - return nil, err - } - - return privateKeyFromBytes(data) -} diff --git a/creds/tokens/credentials.go b/creds/tokens/credentials.go index 903a74f..ac69807 100644 --- a/creds/tokens/credentials.go +++ b/creds/tokens/credentials.go @@ -3,6 +3,7 @@ package tokens import ( "bytes" "context" + "crypto/ecdsa" "errors" "strconv" "sync" @@ -15,7 +16,6 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" - "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" "github.com/nspcc-dev/neofs-sdk-go/pkg/pool" ) @@ -24,11 +24,11 @@ type ( Credentials interface { GetBearerToken(context.Context, *object.Address) (*token.BearerToken, error) GetSessionToken(context.Context, *object.Address) (*session.Token, error) - Put(context.Context, *cid.ID, *owner.ID, *accessbox.AccessBox, ...hcs.PublicKey) (*object.Address, error) + Put(context.Context, *cid.ID, *owner.ID, *accessbox.AccessBox, ...*ecdsa.PublicKey) (*object.Address, error) } cred struct { - key hcs.PrivateKey + key *ecdsa.PrivateKey pool pool.Pool } ) @@ -49,7 +49,7 @@ var bufferPool = sync.Pool{ var _ = New // New creates new Credentials instance using given cli and key. -func New(conns pool.Pool, key hcs.PrivateKey) Credentials { +func New(conns pool.Pool, key *ecdsa.PrivateKey) Credentials { return &cred{pool: conns, key: key} } @@ -112,7 +112,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 ...hcs.PublicKey) (*object.Address, error) { +func (c *cred) Put(ctx context.Context, cid *cid.ID, issuer *owner.ID, box *accessbox.AccessBox, keys ...*ecdsa.PublicKey) (*object.Address, error) { var ( err error created = strconv.FormatInt(time.Now().Unix(), 10)