From dbe65ae6021683b76eaccce6798cb03e7f5e860d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 25 May 2021 22:59:21 +0300 Subject: [PATCH] creds: move credential management into s3 gate Mostly taken from old SDK (abe47687cd11266f946cad57f07572cc10c67226), but error handling adapted to eliminate pkg/errors and internal packages. Signed-off-by: Roman Khimov --- api/auth/center.go | 4 +- api/layer/layer.go | 2 +- authmate/authmate.go | 6 +- cmd/authmate/main.go | 4 +- cmd/s3-gw/app.go | 4 +- creds/accessbox/accessbox.go | 25 +++++ creds/accessbox/bearer_token.go | 38 +++++++ creds/accessbox/bearer_token_test.go | 161 +++++++++++++++++++++++++++ creds/accessbox/decoder.go | 86 ++++++++++++++ creds/accessbox/encoder.go | 85 ++++++++++++++ creds/bearer/credentials.go | 105 +++++++++++++++++ creds/hcs/credentials.go | 82 ++++++++++++++ creds/hcs/public.go | 63 +++++++++++ creds/hcs/secret.go | 60 ++++++++++ creds/neofs/credentials.go | 71 ++++++++++++ creds/neofs/credentials_test.go | 40 +++++++ creds/s3/secret.go | 19 ++++ 17 files changed, 845 insertions(+), 10 deletions(-) create mode 100644 creds/accessbox/accessbox.go create mode 100644 creds/accessbox/bearer_token.go create mode 100644 creds/accessbox/bearer_token_test.go create mode 100644 creds/accessbox/decoder.go create mode 100644 creds/accessbox/encoder.go create mode 100644 creds/bearer/credentials.go create mode 100644 creds/hcs/credentials.go create mode 100644 creds/hcs/public.go create mode 100644 creds/hcs/secret.go create mode 100644 creds/neofs/credentials.go create mode 100644 creds/neofs/credentials_test.go create mode 100644 creds/s3/secret.go diff --git a/api/auth/center.go b/api/auth/center.go index 43c238b61..cc889a268 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -13,8 +13,8 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/bearer" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/bearer" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" "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" diff --git a/api/layer/layer.go b/api/layer/layer.go index c32711a4f..f93c2a6c5 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -9,7 +9,7 @@ import ( "time" sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/nspcc-dev/cdn-sdk/pool" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/object" diff --git a/authmate/authmate.go b/authmate/authmate.go index 214f7da49..7023c1d6c 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -13,9 +13,9 @@ import ( "time" sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/bearer" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" + "github.com/nspcc-dev/neofs-s3-gw/creds/bearer" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/netmap" diff --git a/cmd/authmate/main.go b/cmd/authmate/main.go index 5317b0e66..2e4529905 100644 --- a/cmd/authmate/main.go +++ b/cmd/authmate/main.go @@ -11,8 +11,8 @@ import ( "time" sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/nspcc-dev/cdn-sdk/pool" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-s3-gw/authmate" diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 2b9c5fc98..ada6a01b6 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -8,8 +8,8 @@ import ( "os" sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/nspcc-dev/cdn-sdk/pool" "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/auth" diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go new file mode 100644 index 000000000..683afd727 --- /dev/null +++ b/creds/accessbox/accessbox.go @@ -0,0 +1,25 @@ +package accessbox + +import "github.com/nspcc-dev/neofs-api-go/pkg/token" + +type ( + Box interface { + Marshal() ([]byte, error) + Unmarshal([]byte) error + } + + Encoder interface { + Encode(Box) error + } + + Decoder interface { + Decode(Box) error + } + + BearerTokenBox interface { + Box + + Token() *token.BearerToken + SetToken(*token.BearerToken) + } +) diff --git a/creds/accessbox/bearer_token.go b/creds/accessbox/bearer_token.go new file mode 100644 index 000000000..f150491d6 --- /dev/null +++ b/creds/accessbox/bearer_token.go @@ -0,0 +1,38 @@ +package accessbox + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/token" +) + +type bearerBox struct { + tkn *token.BearerToken +} + +func NewBearerBox(token *token.BearerToken) BearerTokenBox { + return &bearerBox{tkn: token} +} + +func (b *bearerBox) Marshal() ([]byte, error) { + return b.tkn.Marshal(nil) +} + +func (b *bearerBox) Unmarshal(data []byte) error { + tkn := token.NewBearerToken() + + err := tkn.Unmarshal(data) + if err != nil { + return err + } + + b.SetToken(tkn) + + return nil +} + +func (b *bearerBox) Token() *token.BearerToken { + return b.tkn +} + +func (b *bearerBox) SetToken(tkn *token.BearerToken) { + b.tkn = tkn +} diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go new file mode 100644 index 000000000..7382af7b6 --- /dev/null +++ b/creds/accessbox/bearer_token_test.go @@ -0,0 +1,161 @@ +package accessbox + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/binary" + "strconv" + "testing" + + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" + "github.com/nspcc-dev/neofs-api-go/pkg/token" + "github.com/stretchr/testify/require" +) + +func Test_encrypt_decrypt(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + + 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)) + + data, err := box.Marshal() + require.NoError(t, err) + + encrypted, err := encrypt(cred.PrivateKey(), cred.PublicKey(), data) + require.NoError(t, err) + + decrypted, err := decrypt(cred.PrivateKey(), cred.PublicKey(), encrypted) + require.NoError(t, err) + + require.Equal(t, data, decrypted) +} + +func Test_encrypt_decrypt_step_by_step(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + + 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)) + + data, err := box.Marshal() + require.NoError(t, err) + + buf := new(bytes.Buffer) + _, err = cred.PublicKey().WriteTo(buf) + require.NoError(t, err) + + encrypted, err := encrypt(cred.PrivateKey(), cred.PublicKey(), data) + require.NoError(t, err) + + length := len(encrypted) + temp := make([]byte, length+binary.MaxVarintLen64) + size := binary.PutVarint(temp, int64(length)) + copy(temp[size:], encrypted) + buf.Write(temp[:length+size]) + + sender, err := hcs.NewPublicKeyFromReader(buf) + require.NoError(t, err) + + require.Equal(t, cred.PublicKey(), sender) + + ln, err := binary.ReadVarint(buf) + require.NoError(t, err) + require.Equal(t, int64(length), ln) + + enc := make([]byte, ln) + n, err := buf.Read(enc) + require.NoError(t, err) + require.Equal(t, length, n) + require.Equal(t, encrypted, enc) + + decrypted, err := decrypt(cred.PrivateKey(), sender, enc) + require.NoError(t, err) + require.Equal(t, data, decrypted) +} + +func TestSingleKey_AccessBox(t *testing.T) { + tkn := token.NewBearerToken() + expect := NewBearerBox(tkn) + actual := NewBearerBox(nil) + + 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)) + + data, err := Encode(expect, cred.PrivateKey(), cred.PublicKey()) + require.NoError(t, err) + + require.NoError(t, Decode(data, actual, cred.PrivateKey())) + require.Equal(t, expect, actual) +} + +func TestBearerToken_AccessBox(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + 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) + { // generate keys + for i := 0; i < count; i++ { + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + pubs = append(pubs, cred.PublicKey()) + keys = append(keys, cred.PrivateKey()) + } + } + + buf := new(bytes.Buffer) + require.NoError(t, NewEncoder(buf, cred.PrivateKey(), pubs...).Encode(box)) + + data := buf.Bytes() + + for i := range keys { + key := keys[i] + t.Run("try with key "+strconv.Itoa(i), func(t *testing.T) { + r := bytes.NewReader(data) + nbx := NewBearerBox(nil) + require.NoError(t, NewDecoder(r, key).Decode(nbx)) + require.Equal(t, tkn, nbx.Token()) + }) + } + + t.Run("should fail for unknown key", func(t *testing.T) { + cred, err = hcs.Generate(rand.Reader) + require.NoError(t, err) + + r := bytes.NewReader(data) + nbx := NewBearerBox(nil) + require.EqualError(t, NewDecoder(r, cred.PrivateKey()).Decode(nbx), "chacha20poly1305: message authentication failed") + }) +} diff --git a/creds/accessbox/decoder.go b/creds/accessbox/decoder.go new file mode 100644 index 000000000..241b50375 --- /dev/null +++ b/creds/accessbox/decoder.go @@ -0,0 +1,86 @@ +package accessbox + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +type decoder struct { + *bufio.Reader + + key hcs.PrivateKey +} + +func NewDecoder(r io.Reader, key hcs.PrivateKey) Decoder { + return &decoder{Reader: bufio.NewReader(r), key: key} +} + +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) + if err != nil { + return nil, err + } + + if ld, ns := len(data), dec.NonceSize(); ld < ns { + return nil, fmt.Errorf("wrong data size (%d), should be greater than %d", ld, ns) + } + + nonce, cypher := data[:dec.NonceSize()], data[dec.NonceSize():] + return dec.Open(nil, nonce, cypher, nil) +} + +func (d *decoder) Decode(box Box) error { + sender, err := hcs.NewPublicKeyFromReader(d) + if err != nil { + return err + } + + var lastErr error + + for { + size, err := binary.ReadVarint(d) + if err == io.EOF { + break + } else if err != nil { + return err + } + + data := make([]byte, size) + + if ln, err := d.Read(data); err != nil { + lastErr = err + continue + } else if ln != int(size) { + lastErr = fmt.Errorf("expect %d bytes, but read only %d bytes", size, ln) + continue + } else if decoded, err := decrypt(d.key, sender, data); err != nil { + lastErr = err + continue + } else if err = box.Unmarshal(decoded); err != nil { + lastErr = err + continue + } + + return nil + } + + return lastErr +} + +func Decode(data []byte, box Box, owner hcs.PrivateKey) error { + return NewDecoder(bytes.NewBuffer(data), owner).Decode(box) +} diff --git a/creds/accessbox/encoder.go b/creds/accessbox/encoder.go new file mode 100644 index 000000000..24bffaa80 --- /dev/null +++ b/creds/accessbox/encoder.go @@ -0,0 +1,85 @@ +package accessbox + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "fmt" + "io" + + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +type encoder struct { + io.Writer + + owner hcs.PrivateKey + keys []hcs.PublicKey +} + +// NewEncoder creates encoder +func NewEncoder(w io.Writer, owner hcs.PrivateKey, keys ...hcs.PublicKey) Encoder { + return &encoder{ + Writer: w, + owner: owner, + keys: keys, + } +} + +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 + } + + enc, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + + nonce := make([]byte, enc.NonceSize(), enc.NonceSize()+len(data)+enc.Overhead()) + if _, err := rand.Read(nonce); err != nil { + return nil, err + } + + return enc.Seal(nonce, nonce, data, nil), nil +} + +// Encode and encrypt box through owner private key and public keys. +func (e *encoder) Encode(box Box) error { + data, err := box.Marshal() + if err != nil { + return err + } + + // write owner public key + if _, err = e.owner.PublicKey().WriteTo(e); err != nil { + return err + } + + for i, sender := range e.keys { + encrypted, err := encrypt(e.owner, sender, data) + if err != nil { + return fmt.Errorf("%w, sender = %d", err, i) + } + + ln := len(encrypted) + temp := make([]byte, ln+binary.MaxVarintLen64) + size := binary.PutVarint(temp, int64(ln)) + copy(temp[size:], encrypted) + if _, err := e.Write(temp[:size+ln]); err != nil { + return fmt.Errorf("%w, sender = %d", err, i) + } + } + + return nil +} + +// Encode and encrypt box through owner private key and public keys. +func Encode(box Box, owner hcs.PrivateKey, keys ...hcs.PublicKey) ([]byte, error) { + buf := new(bytes.Buffer) + err := NewEncoder(buf, owner, keys...).Encode(box) + return buf.Bytes(), err +} diff --git a/creds/bearer/credentials.go b/creds/bearer/credentials.go new file mode 100644 index 000000000..458ae23e7 --- /dev/null +++ b/creds/bearer/credentials.go @@ -0,0 +1,105 @@ +package bearer + +import ( + "bytes" + "context" + "errors" + "strconv" + "sync" + "time" + + sdk "github.com/nspcc-dev/cdn-sdk" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "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/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" +) + +type ( + Credentials interface { + Get(context.Context, *object.Address) (*token.BearerToken, error) + Put(context.Context, *container.ID, *token.BearerToken, ...hcs.PublicKey) (*object.Address, error) + } + + cred struct { + key hcs.PrivateKey + obj sdk.ObjectClient + } +) + +var ( + ErrEmptyPublicKeys = errors.New("HCS public keys could not be empty") + ErrEmptyBearerToken = errors.New("Bearer token could not be empty") +) + +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +var _ = New + +func New(cli sdk.ObjectClient, key hcs.PrivateKey) Credentials { + return &cred{obj: cli, key: key} +} + +func (c *cred) acquireBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func (c *cred) releaseBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +func (c *cred) Get(ctx context.Context, address *object.Address) (*token.BearerToken, error) { + buf := c.acquireBuffer() + defer c.releaseBuffer(buf) + + box := accessbox.NewBearerBox(nil) + + if _, err := c.obj.Get(ctx, address, sdk.WithGetWriter(buf)); err != nil { + return nil, err + } else if err = accessbox.NewDecoder(buf, c.key).Decode(box); err != nil { + return nil, err + } + + return box.Token(), nil +} + +func (c *cred) Put(ctx context.Context, cid *container.ID, tkn *token.BearerToken, keys ...hcs.PublicKey) (*object.Address, error) { + var ( + err error + buf = c.acquireBuffer() + box = accessbox.NewBearerBox(tkn) + + created = strconv.FormatInt(time.Now().Unix(), 10) + ) + + defer c.releaseBuffer(buf) + + if len(keys) == 0 { + return nil, ErrEmptyPublicKeys + } else if tkn == nil { + return nil, ErrEmptyBearerToken + } else if err = accessbox.NewEncoder(buf, c.key, keys...).Encode(box); err != nil { + return nil, err + } + + timestamp := object.NewAttribute() + timestamp.SetKey(object.AttributeTimestamp) + timestamp.SetValue(created) + + filename := object.NewAttribute() + filename.SetKey(object.AttributeFileName) + filename.SetValue(created + "_access.box") + + raw := object.NewRaw() + raw.SetContainerID(cid) + raw.SetOwnerID(tkn.Issuer()) + raw.SetAttributes(filename, timestamp) + + return c.obj.Put(ctx, raw.Object(), sdk.WithPutReader(buf)) +} diff --git a/creds/hcs/credentials.go b/creds/hcs/credentials.go new file mode 100644 index 000000000..0ef0d457f --- /dev/null +++ b/creds/hcs/credentials.go @@ -0,0 +1,82 @@ +package hcs + +import ( + "errors" + "io" + + "golang.org/x/crypto/curve25519" +) + +type ( + Credentials interface { + PublicKey() PublicKey + PrivateKey() PrivateKey + } + + keyer interface { + io.WriterTo + + Bytes() []byte + String() string + } + + PublicKey interface { + keyer + } + + PrivateKey interface { + keyer + + PublicKey() PublicKey + } + + credentials struct { + public PublicKey + secret PrivateKey + } + + public []byte + secret []byte +) + +var ErrEmptyCredentials = errors.New("empty credentials") + +var _ = NewCredentials + +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 +} + +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 +} + +func (c *credentials) PublicKey() PublicKey { + return c.public +} + +func (c *credentials) PrivateKey() PrivateKey { + return c.secret +} diff --git a/creds/hcs/public.go b/creds/hcs/public.go new file mode 100644 index 000000000..e57daa71d --- /dev/null +++ b/creds/hcs/public.go @@ -0,0 +1,63 @@ +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 +} + +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) +} + +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) +} + +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 new file mode 100644 index 000000000..1236c42d9 --- /dev/null +++ b/creds/hcs/secret.go @@ -0,0 +1,60 @@ +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/neofs/credentials.go b/creds/neofs/credentials.go new file mode 100644 index 000000000..fa25864bb --- /dev/null +++ b/creds/neofs/credentials.go @@ -0,0 +1,71 @@ +package neofs + +import ( + "crypto/ecdsa" + + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type ( + // Credentials contains methods that needed to work with NeoFS. + Credentials interface { + WIF() string + Owner() *owner.ID + PublicKey() *ecdsa.PublicKey + PrivateKey() *ecdsa.PrivateKey + } + + cred struct { + key *ecdsa.PrivateKey + owner *owner.ID + wif string + } +) + +// New creates an instance of Credentials through string representation of secret. +// It allows passing WIF, path, hex-encoded and others. +func New(secret string) (Credentials, error) { + key, err := crypto.LoadPrivateKey(secret) + if err != nil { + return nil, err + } + + return setFromPrivateKey(key) +} + +// PrivateKey returns ecdsa.PrivateKey. +func (c *cred) PrivateKey() *ecdsa.PrivateKey { + return c.key +} + +// PublicKey returns ecdsa.PublicKey. +func (c *cred) PublicKey() *ecdsa.PublicKey { + return &c.key.PublicKey +} + +// Owner returns owner.ID. +func (c *cred) Owner() *owner.ID { + return c.owner +} + +// WIF returns string representation of WIF. +func (c *cred) WIF() string { + return c.wif +} + +func setFromPrivateKey(key *ecdsa.PrivateKey) (*cred, error) { + wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) + if err != nil { + return nil, err + } + + ownerID := owner.NewIDFromNeo3Wallet(wallet) + + wif, err := crypto.WIFEncode(key) + if err != nil { + return nil, err + } + + return &cred{key: key, owner: ownerID, wif: wif}, nil +} diff --git a/creds/neofs/credentials_test.go b/creds/neofs/credentials_test.go new file mode 100644 index 000000000..82e15ae1f --- /dev/null +++ b/creds/neofs/credentials_test.go @@ -0,0 +1,40 @@ +package neofs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + t.Run("should fail", func(t *testing.T) { + cred, err := New("") + require.Nil(t, cred) + require.Error(t, err) + }) + + t.Run("should work as expected", func(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + wif, err := crypto.WIFEncode(key) + require.NoError(t, err) + + wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) + require.NoError(t, err) + + own := owner.NewIDFromNeo3Wallet(wallet) + + cred, err := New(wif) + require.NoError(t, err) + require.Equal(t, cred.WIF(), wif) + require.Equal(t, cred.Owner(), own) + require.Equal(t, cred.PrivateKey(), key) + require.Equal(t, cred.PublicKey(), &key.PublicKey) + }) +} diff --git a/creds/s3/secret.go b/creds/s3/secret.go new file mode 100644 index 000000000..ffd746275 --- /dev/null +++ b/creds/s3/secret.go @@ -0,0 +1,19 @@ +package s3 + +import ( + "crypto/sha256" + "encoding/hex" + + "github.com/nspcc-dev/neofs-api-go/pkg/token" +) + +// SecretAccessKey returns secret access key generated by BearerToken. +func SecretAccessKey(tkn *token.BearerToken) (string, error) { + data, err := tkn.Marshal() + if err != nil { + return "", err + } + + hash := sha256.Sum256(data) + return hex.EncodeToString(hash[:]), nil +}