From dfc2dd8a78ee2b63b43fb5fab221c7acf5bf71fd Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 11:52:05 +0300 Subject: [PATCH 1/7] session: replace PToken structure with PrivateToken interface In previous implementation PToken contained the full Token structure. Since private token is used for data signature only, storing unused fields of a user token is impractical. To emphasize the purpose of the private part of the session, it makes sense to provide the user of the session package with its interface. The interface will only provide the functionality of data signing with private session key. This commit: * removes PToken structure from session package; * defines PrivateToken interface of private session part; * adds the implementation of PrivateToken on unexported struct; * provides the constructor that generates session key internally. --- session/private.go | 37 +++++++++++++++++++++++++++++++++++++ session/private_test.go | 33 +++++++++++++++++++++++++++++++++ session/service.go | 4 ++-- session/store.go | 14 ++++++-------- session/types.go | 30 ++++++++++++------------------ 5 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 session/private.go create mode 100644 session/private_test.go diff --git a/session/private.go b/session/private.go new file mode 100644 index 0000000..4d4f3c2 --- /dev/null +++ b/session/private.go @@ -0,0 +1,37 @@ +package session + +import ( + "crypto/ecdsa" + "crypto/rand" + + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type pToken struct { + // private session token + sessionKey *ecdsa.PrivateKey +} + +// NewSessionPrivateToken creates PrivateToken instance. +// +// Returns non-nil error on key generation error. +func NewPrivateToken() (PrivateToken, error) { + sk, err := ecdsa.GenerateKey(defaultCurve(), 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..915abb4 100644 --- a/session/service.go +++ b/session/service.go @@ -17,10 +17,10 @@ type ( // TokenStore is a PToken storage manipulation interface. TokenStore interface { // New returns new token with specified parameters. - New(p TokenParams) *PToken + New(p TokenParams) PrivateToken // Fetch tries to fetch a token with specified id. - Fetch(id TokenID) *PToken + Fetch(id TokenID) PrivateToken // Remove removes token with id from store. Remove(id TokenID) diff --git a/session/store.go b/session/store.go index 7c56c39..440fbb5 100644 --- a/session/store.go +++ b/session/store.go @@ -13,7 +13,7 @@ import ( type simpleStore struct { *sync.RWMutex - tokens map[TokenID]*PToken + tokens map[TokenID]PrivateToken } // TODO get curve from neofs-crypto @@ -25,12 +25,12 @@ func defaultCurve() elliptic.Curve { func NewSimpleStore() TokenStore { return &simpleStore{ 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 { +func (s *simpleStore) New(p TokenParams) PrivateToken { tid, err := refs.NewUUID() if err != nil { return nil @@ -54,10 +54,8 @@ func (s *simpleStore) New(p TokenParams) *PToken { token.SetExpirationEpoch(p.LastEpoch) token.SetSessionKey(crypto.MarshalPublicKey(&key.PublicKey)) - t := &PToken{ - mtx: new(sync.Mutex), - Token: *token, - PrivateKey: key, + t := &pToken{ + sessionKey: key, } s.Lock() @@ -68,7 +66,7 @@ func (s *simpleStore) New(p TokenParams) *PToken { } // Fetch tries to fetch a token with specified id. -func (s *simpleStore) Fetch(id TokenID) *PToken { +func (s *simpleStore) Fetch(id TokenID) PrivateToken { s.RLock() defer s.RUnlock() diff --git a/session/types.go b/session/types.go index e56373c..e7e4b2a 100644 --- a/session/types.go +++ b/session/types.go @@ -1,13 +1,9 @@ package session import ( - "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 ( @@ -23,17 +19,20 @@ type ( Address = refs.Address // Verb is Token_Info_Verb type alias Verb = service.Token_Info_Verb - - // 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 - } ) +// PrivateToken is an interface of session private part. +type PrivateToken interface { + // PublicKey must return a binary representation of session public key. + PublicKey() []byte + + // 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) +} + const ( // ErrWrongFirstEpoch is raised when passed Token contains wrong first epoch. // First epoch is an epoch since token is valid @@ -58,8 +57,3 @@ const ( // 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) -} From 22265a9f038d27ea283cdf753feb46094b38cfe4 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:39:41 +0300 Subject: [PATCH 2/7] session: refactor token store This commit: * changes the set and signatures of TokenStore interface methods; * adds unit test for map storage. --- session/service.go | 21 ------------- session/store.go | 70 +++++++++++++------------------------------ session/store_test.go | 34 ++++++++++++++++++++- session/types.go | 20 +++++++++++++ 4 files changed, 74 insertions(+), 71 deletions(-) diff --git a/session/service.go b/session/service.go index 915abb4..ecbd6f5 100644 --- a/session/service.go +++ b/session/service.go @@ -13,27 +13,6 @@ type ( 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) PrivateToken - - // Fetch tries to fetch a token with specified id. - Fetch(id TokenID) PrivateToken - - // 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. diff --git a/session/store.go b/session/store.go index 440fbb5..fa3ca69 100644 --- a/session/store.go +++ b/session/store.go @@ -1,16 +1,11 @@ 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]PrivateToken @@ -21,61 +16,38 @@ 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]PrivateToken), } } -// New returns new token with specified parameters. -func (s *simpleStore) New(p TokenParams) PrivateToken { - 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{ - sessionKey: 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) PrivateToken { +// 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 e7e4b2a..bacc770 100644 --- a/session/types.go +++ b/session/types.go @@ -33,6 +33,26 @@ type PrivateToken interface { 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 +} + +const ErrPrivateTokenNotFound = internal.Error("private token not found") + const ( // ErrWrongFirstEpoch is raised when passed Token contains wrong first epoch. // First epoch is an epoch since token is valid From 701bbafcf11724216078739990363167d8f85cc6 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:44:35 +0300 Subject: [PATCH 3/7] session: change KeyStore documentation --- session/service.go | 15 --------------- session/types.go | 11 +++++++++++ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/session/service.go b/session/service.go index ecbd6f5..6e293d3 100644 --- a/session/service.go +++ b/session/service.go @@ -1,20 +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) - } -) - // NewInitRequest returns new initialization CreateRequest from passed Token. func NewInitRequest(t *Token) *CreateRequest { return &CreateRequest{Message: &CreateRequest_Init{Init: t}} diff --git a/session/types.go b/session/types.go index bacc770..435616a 100644 --- a/session/types.go +++ b/session/types.go @@ -1,6 +1,9 @@ package session import ( + "context" + "crypto/ecdsa" + "github.com/nspcc-dev/neofs-api-go/internal" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" @@ -51,6 +54,14 @@ type PrivateTokenStore interface { 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) +} + const ErrPrivateTokenNotFound = internal.Error("private token not found") const ( From f0867036fbd3818a1c7d2d38b5692fe73244260c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:46:05 +0300 Subject: [PATCH 4/7] session: remove trivial defaultCurve function --- session/private.go | 3 ++- session/store.go | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/session/private.go b/session/private.go index 4d4f3c2..a159227 100644 --- a/session/private.go +++ b/session/private.go @@ -2,6 +2,7 @@ package session import ( "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" crypto "github.com/nspcc-dev/neofs-crypto" @@ -16,7 +17,7 @@ type pToken struct { // // Returns non-nil error on key generation error. func NewPrivateToken() (PrivateToken, error) { - sk, err := ecdsa.GenerateKey(defaultCurve(), rand.Reader) + sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } diff --git a/session/store.go b/session/store.go index fa3ca69..5cd6314 100644 --- a/session/store.go +++ b/session/store.go @@ -1,7 +1,6 @@ package session import ( - "crypto/elliptic" "sync" ) @@ -11,11 +10,6 @@ type mapTokenStore struct { tokens map[TokenID]PrivateToken } -// TODO get curve from neofs-crypto -func defaultCurve() elliptic.Curve { - return elliptic.P256() -} - // NewMapTokenStore creates new PrivateTokenStore instance. // // The elements of the instance are stored in the map. From 79142ada0484e8e5e77b6c5f7c3981d215ab116d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:49:27 +0300 Subject: [PATCH 5/7] session: replace used type aliases to separate file --- session/alias.go | 15 +++++++++++++++ session/types.go | 17 ----------------- 2 files changed, 15 insertions(+), 17 deletions(-) create mode 100644 session/alias.go 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/types.go b/session/types.go index 435616a..a4a1643 100644 --- a/session/types.go +++ b/session/types.go @@ -5,23 +5,6 @@ import ( "crypto/ecdsa" "github.com/nspcc-dev/neofs-api-go/internal" - "github.com/nspcc-dev/neofs-api-go/refs" - "github.com/nspcc-dev/neofs-api-go/service" -) - -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. From 608f5781055a72f392b9cb47bd644ee04b0b1a02 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:50:52 +0300 Subject: [PATCH 6/7] session: removes unused errors --- session/types.go | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/session/types.go b/session/types.go index a4a1643..0f209c7 100644 --- a/session/types.go +++ b/session/types.go @@ -45,29 +45,5 @@ type KeyStore interface { 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") - -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") -) From ffd4338eb38bce519115ca9be3370bbb6dfa3a15 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 12:59:58 +0300 Subject: [PATCH 7/7] session: fix NewPrivateToken function doc --- session/private.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session/private.go b/session/private.go index a159227..5a0ca03 100644 --- a/session/private.go +++ b/session/private.go @@ -13,7 +13,7 @@ type pToken struct { sessionKey *ecdsa.PrivateKey } -// NewSessionPrivateToken creates PrivateToken instance. +// NewPrivateToken creates PrivateToken instance. // // Returns non-nil error on key generation error. func NewPrivateToken() (PrivateToken, error) {