forked from TrueCloudLab/frostfs-s3-gw
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 <roman@nspcc.ru>
This commit is contained in:
parent
ce7c8932d4
commit
dbe65ae602
17 changed files with 845 additions and 10 deletions
|
@ -13,8 +13,8 @@ import (
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||||
sdk "github.com/nspcc-dev/cdn-sdk"
|
sdk "github.com/nspcc-dev/cdn-sdk"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/bearer"
|
"github.com/nspcc-dev/neofs-s3-gw/creds/bearer"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/hcs"
|
"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/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/authmate"
|
"github.com/nspcc-dev/neofs-s3-gw/authmate"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/nspcc-dev/cdn-sdk"
|
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/cdn-sdk/pool"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"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/object"
|
||||||
|
|
|
@ -13,9 +13,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/nspcc-dev/cdn-sdk"
|
sdk "github.com/nspcc-dev/cdn-sdk"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/bearer"
|
"github.com/nspcc-dev/neofs-s3-gw/creds/bearer"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/hcs"
|
"github.com/nspcc-dev/neofs-s3-gw/creds/hcs"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/neofs"
|
"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/acl/eacl"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/nspcc-dev/cdn-sdk"
|
sdk "github.com/nspcc-dev/cdn-sdk"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/hcs"
|
"github.com/nspcc-dev/neofs-s3-gw/creds/hcs"
|
||||||
"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/cdn-sdk/pool"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/authmate"
|
"github.com/nspcc-dev/neofs-s3-gw/authmate"
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
sdk "github.com/nspcc-dev/cdn-sdk"
|
sdk "github.com/nspcc-dev/cdn-sdk"
|
||||||
"github.com/nspcc-dev/cdn-sdk/creds/hcs"
|
"github.com/nspcc-dev/neofs-s3-gw/creds/hcs"
|
||||||
"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/cdn-sdk/pool"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
"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/auth"
|
||||||
|
|
25
creds/accessbox/accessbox.go
Normal file
25
creds/accessbox/accessbox.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
38
creds/accessbox/bearer_token.go
Normal file
38
creds/accessbox/bearer_token.go
Normal file
|
@ -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
|
||||||
|
}
|
161
creds/accessbox/bearer_token_test.go
Normal file
161
creds/accessbox/bearer_token_test.go
Normal file
|
@ -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")
|
||||||
|
})
|
||||||
|
}
|
86
creds/accessbox/decoder.go
Normal file
86
creds/accessbox/decoder.go
Normal file
|
@ -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)
|
||||||
|
}
|
85
creds/accessbox/encoder.go
Normal file
85
creds/accessbox/encoder.go
Normal file
|
@ -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
|
||||||
|
}
|
105
creds/bearer/credentials.go
Normal file
105
creds/bearer/credentials.go
Normal file
|
@ -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))
|
||||||
|
}
|
82
creds/hcs/credentials.go
Normal file
82
creds/hcs/credentials.go
Normal file
|
@ -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
|
||||||
|
}
|
63
creds/hcs/public.go
Normal file
63
creds/hcs/public.go
Normal file
|
@ -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)
|
||||||
|
}
|
60
creds/hcs/secret.go
Normal file
60
creds/hcs/secret.go
Normal file
|
@ -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)
|
||||||
|
}
|
71
creds/neofs/credentials.go
Normal file
71
creds/neofs/credentials.go
Normal file
|
@ -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
|
||||||
|
}
|
40
creds/neofs/credentials_test.go
Normal file
40
creds/neofs/credentials_test.go
Normal file
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
19
creds/s3/secret.go
Normal file
19
creds/s3/secret.go
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue