diff --git a/session/alias.go b/session/alias.go new file mode 100644 index 0000000..aa49d55 --- /dev/null +++ b/session/alias.go @@ -0,0 +1,15 @@ +package session + +import ( + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-api-go/service" +) + +// OwnerID is a type alias of OwnerID ref. +type OwnerID = refs.OwnerID + +// TokenID is a type alias of TokenID ref. +type TokenID = service.TokenID + +// Token is a type alias of Token. +type Token = service.Token diff --git a/session/private.go b/session/private.go new file mode 100644 index 0000000..5a0ca03 --- /dev/null +++ b/session/private.go @@ -0,0 +1,38 @@ +package session + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type pToken struct { + // private session token + sessionKey *ecdsa.PrivateKey +} + +// NewPrivateToken creates PrivateToken instance. +// +// Returns non-nil error on key generation error. +func NewPrivateToken() (PrivateToken, error) { + sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + + return &pToken{ + sessionKey: sk, + }, nil +} + +// Sign signs data with session private key. +func (t *pToken) Sign(data []byte) ([]byte, error) { + return crypto.Sign(t.sessionKey, data) +} + +// PublicKey returns a binary representation of the session public key. +func (t *pToken) PublicKey() []byte { + return crypto.MarshalPublicKey(&t.sessionKey.PublicKey) +} diff --git a/session/private_test.go b/session/private_test.go new file mode 100644 index 0000000..f0fb9f4 --- /dev/null +++ b/session/private_test.go @@ -0,0 +1,33 @@ +package session + +import ( + "crypto/rand" + "testing" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/stretchr/testify/require" +) + +func TestPrivateToken(t *testing.T) { + // create new private token + pToken, err := NewPrivateToken() + require.NoError(t, err) + + // generate data to sign + data := make([]byte, 10) + _, err = rand.Read(data) + require.NoError(t, err) + + // sign data via private token + sig, err := pToken.Sign(data) + require.NoError(t, err) + + // check signature + require.NoError(t, + crypto.Verify( + crypto.UnmarshalPublicKey(pToken.PublicKey()), + data, + sig, + ), + ) +} diff --git a/session/service.go b/session/service.go index 367aeb1..6e293d3 100644 --- a/session/service.go +++ b/session/service.go @@ -1,41 +1,5 @@ package session -import ( - "context" - "crypto/ecdsa" - - "github.com/nspcc-dev/neofs-api-go/refs" -) - -type ( - // KeyStore is an interface that describes storage, - // that allows to fetch public keys by OwnerID. - KeyStore interface { - Get(ctx context.Context, id refs.OwnerID) ([]*ecdsa.PublicKey, error) - } - - // TokenStore is a PToken storage manipulation interface. - TokenStore interface { - // New returns new token with specified parameters. - New(p TokenParams) *PToken - - // Fetch tries to fetch a token with specified id. - Fetch(id TokenID) *PToken - - // Remove removes token with id from store. - Remove(id TokenID) - } - - // TokenParams contains params to create new PToken. - TokenParams struct { - FirstEpoch uint64 - LastEpoch uint64 - Address Address - OwnerID OwnerID - Verb Verb - } -) - // NewInitRequest returns new initialization CreateRequest from passed Token. func NewInitRequest(t *Token) *CreateRequest { return &CreateRequest{Message: &CreateRequest_Init{Init: t}} diff --git a/session/store.go b/session/store.go index 7c56c39..5cd6314 100644 --- a/session/store.go +++ b/session/store.go @@ -1,83 +1,47 @@ package session import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "sync" - - "github.com/nspcc-dev/neofs-api-go/refs" - crypto "github.com/nspcc-dev/neofs-crypto" ) -type simpleStore struct { +type mapTokenStore struct { *sync.RWMutex - tokens map[TokenID]*PToken + tokens map[TokenID]PrivateToken } -// TODO get curve from neofs-crypto -func defaultCurve() elliptic.Curve { - return elliptic.P256() -} - -// NewSimpleStore creates simple token storage -func NewSimpleStore() TokenStore { - return &simpleStore{ +// NewMapTokenStore creates new PrivateTokenStore instance. +// +// The elements of the instance are stored in the map. +func NewMapTokenStore() PrivateTokenStore { + return &mapTokenStore{ RWMutex: new(sync.RWMutex), - tokens: make(map[TokenID]*PToken), + tokens: make(map[TokenID]PrivateToken), } } -// New returns new token with specified parameters. -func (s *simpleStore) New(p TokenParams) *PToken { - tid, err := refs.NewUUID() - if err != nil { - return nil - } - - key, err := ecdsa.GenerateKey(defaultCurve(), rand.Reader) - if err != nil { - return nil - } - - if p.FirstEpoch > p.LastEpoch || p.OwnerID.Empty() { - return nil - } - - token := new(Token) - token.SetID(tid) - token.SetOwnerID(p.OwnerID) - token.SetVerb(p.Verb) - token.SetAddress(p.Address) - token.SetCreationEpoch(p.FirstEpoch) - token.SetExpirationEpoch(p.LastEpoch) - token.SetSessionKey(crypto.MarshalPublicKey(&key.PublicKey)) - - t := &PToken{ - mtx: new(sync.Mutex), - Token: *token, - PrivateKey: key, - } - +// Store adds passed token to the map. +// +// Resulting error is always nil. +func (s *mapTokenStore) Store(id TokenID, token PrivateToken) error { s.Lock() - s.tokens[tid] = t + s.tokens[id] = token s.Unlock() - return t + return nil } -// Fetch tries to fetch a token with specified id. -func (s *simpleStore) Fetch(id TokenID) *PToken { +// Fetch returns the map element corresponding to the given key. +// +// Returns ErrPrivateTokenNotFound is there is no element in map. +func (s *mapTokenStore) Fetch(id TokenID) (PrivateToken, error) { s.RLock() defer s.RUnlock() - return s.tokens[id] -} + t, ok := s.tokens[id] + if !ok { + return nil, ErrPrivateTokenNotFound + } -// Remove removes token with id from store. -func (s *simpleStore) Remove(id TokenID) { - s.Lock() - delete(s.tokens, id) - s.Unlock() + return t, nil } diff --git a/session/store_test.go b/session/store_test.go index f51fb18..37d742e 100644 --- a/session/store_test.go +++ b/session/store_test.go @@ -1,3 +1,35 @@ package session -// TODO: write unit tests +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/stretchr/testify/require" +) + +func TestMapTokenStore(t *testing.T) { + // create new private token + pToken, err := NewPrivateToken() + require.NoError(t, err) + + // create map token store + s := NewMapTokenStore() + + // create new storage key + id, err := refs.NewUUID() + require.NoError(t, err) + + // ascertain that there is no record for the key + _, err = s.Fetch(id) + require.EqualError(t, err, ErrPrivateTokenNotFound.Error()) + + // save private token record + require.NoError(t, s.Store(id, pToken)) + + // fetch private token by the key + res, err := s.Fetch(id) + require.NoError(t, err) + + // ascertain that returned token equals to initial + require.Equal(t, pToken, res) +} diff --git a/session/types.go b/session/types.go index e56373c..0f209c7 100644 --- a/session/types.go +++ b/session/types.go @@ -1,65 +1,49 @@ package session import ( + "context" "crypto/ecdsa" - "sync" "github.com/nspcc-dev/neofs-api-go/internal" - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-api-go/service" - crypto "github.com/nspcc-dev/neofs-crypto" ) -type ( - // ObjectID type alias. - ObjectID = refs.ObjectID - // OwnerID type alias. - OwnerID = refs.OwnerID - // TokenID type alias. - TokenID = refs.UUID - // Token type alias - Token = service.Token - // Address type alias - Address = refs.Address - // Verb is Token_Info_Verb type alias - Verb = service.Token_Info_Verb +// PrivateToken is an interface of session private part. +type PrivateToken interface { + // PublicKey must return a binary representation of session public key. + PublicKey() []byte - // PToken is a wrapper around Token that allows to sign data - // and to do thread-safe manipulations. - PToken struct { - Token - - mtx *sync.Mutex - PrivateKey *ecdsa.PrivateKey - } -) - -const ( - // ErrWrongFirstEpoch is raised when passed Token contains wrong first epoch. - // First epoch is an epoch since token is valid - ErrWrongFirstEpoch = internal.Error("wrong first epoch") - - // ErrWrongLastEpoch is raised when passed Token contains wrong last epoch. - // Last epoch is an epoch until token is valid - ErrWrongLastEpoch = internal.Error("wrong last epoch") - - // ErrWrongOwner is raised when passed Token contains wrong OwnerID. - ErrWrongOwner = internal.Error("wrong owner") - - // ErrEmptyPublicKey is raised when passed Token contains wrong public key. - ErrEmptyPublicKey = internal.Error("empty public key") - - // ErrWrongObjectsCount is raised when passed Token contains wrong objects count. - ErrWrongObjectsCount = internal.Error("wrong objects count") - - // ErrWrongObjects is raised when passed Token contains wrong object ids. - ErrWrongObjects = internal.Error("wrong objects") - - // ErrInvalidSignature is raised when wrong signature is passed to VerificationHeader.VerifyData(). - ErrInvalidSignature = internal.Error("invalid signature") -) - -// SignData signs data with session private key. -func (t *PToken) SignData(data []byte) ([]byte, error) { - return crypto.Sign(t.PrivateKey, data) + // Sign must return the signature of passed data. + // + // Resulting signature must be verified by crypto.Verify function + // with the session public key. + Sign([]byte) ([]byte, error) } + +// PrivateTokenSource is an interface of private token storage with read access. +type PrivateTokenSource interface { + // Fetch must return the storage record corresponding to the passed key. + // + // Resulting error must be ErrPrivateTokenNotFound if there is no corresponding record. + Fetch(TokenID) (PrivateToken, error) +} + +// PrivateTokenStore is an interface of the storage of private tokens addressable by TokenID. +type PrivateTokenStore interface { + PrivateTokenSource + + // Store must save passed private token in the storage under the given key. + // + // Resulting error must be nil if private token was stored successfully. + Store(TokenID, PrivateToken) error +} + +// KeyStore is an interface of the storage of public keys addressable by OwnerID, +type KeyStore interface { + // Get must return the storage record corresponding to the passed key. + // + // Resulting error must be ErrKeyNotFound if there is no corresponding record. + Get(context.Context, OwnerID) ([]*ecdsa.PublicKey, error) +} + +// ErrPrivateTokenNotFound is raised when addressed private token was not found in storage. +const ErrPrivateTokenNotFound = internal.Error("private token not found")