Merge pull request #73 from nspcc-dev/session-private-token-expiration

session: support the expiration of private tokens
This commit is contained in:
Leonard Lyubich 2020-04-29 14:54:43 +03:00 committed by GitHub
commit e3cab218dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 4 deletions

View file

@ -11,12 +11,14 @@ import (
type pToken struct { type pToken struct {
// private session token // private session token
sessionKey *ecdsa.PrivateKey sessionKey *ecdsa.PrivateKey
// last epoch of the lifetime
validUntil uint64
} }
// NewPrivateToken creates PrivateToken instance. // NewPrivateToken creates PrivateToken instance that expires after passed epoch.
// //
// Returns non-nil error on key generation error. // Returns non-nil error on key generation error.
func NewPrivateToken() (PrivateToken, error) { func NewPrivateToken(validUntil uint64) (PrivateToken, error) {
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { if err != nil {
return nil, err return nil, err
@ -24,6 +26,7 @@ func NewPrivateToken() (PrivateToken, error) {
return &pToken{ return &pToken{
sessionKey: sk, sessionKey: sk,
validUntil: validUntil,
}, nil }, nil
} }
@ -36,3 +39,7 @@ func (t *pToken) Sign(data []byte) ([]byte, error) {
func (t *pToken) PublicKey() []byte { func (t *pToken) PublicKey() []byte {
return crypto.MarshalPublicKey(&t.sessionKey.PublicKey) return crypto.MarshalPublicKey(&t.sessionKey.PublicKey)
} }
func (t *pToken) Expired(epoch uint64) bool {
return t.validUntil < epoch
}

View file

@ -10,7 +10,7 @@ import (
func TestPrivateToken(t *testing.T) { func TestPrivateToken(t *testing.T) {
// create new private token // create new private token
pToken, err := NewPrivateToken() pToken, err := NewPrivateToken(0)
require.NoError(t, err) require.NoError(t, err)
// generate data to sign // generate data to sign
@ -31,3 +31,20 @@ func TestPrivateToken(t *testing.T) {
), ),
) )
} }
func TestPToken_Expired(t *testing.T) {
e := uint64(10)
var token PrivateToken = &pToken{
validUntil: e,
}
// must not be expired in the epoch before last
require.False(t, token.Expired(e-1))
// must not be expired in the last epoch
require.False(t, token.Expired(e))
// must be expired in the epoch after last
require.True(t, token.Expired(e+1))
}

View file

@ -45,3 +45,20 @@ func (s *mapTokenStore) Fetch(id TokenID) (PrivateToken, error) {
return t, nil return t, nil
} }
// RemoveExpired removes all the map elements that are expired in the passed epoch.
//
// Resulting error is always nil.
func (s *mapTokenStore) RemoveExpired(epoch uint64) error {
s.Lock()
for key, token := range s.tokens {
if token.Expired(epoch) {
delete(s.tokens, key)
}
}
s.Unlock()
return nil
}

View file

@ -9,7 +9,7 @@ import (
func TestMapTokenStore(t *testing.T) { func TestMapTokenStore(t *testing.T) {
// create new private token // create new private token
pToken, err := NewPrivateToken() pToken, err := NewPrivateToken(0)
require.NoError(t, err) require.NoError(t, err)
// create map token store // create map token store
@ -33,3 +33,64 @@ func TestMapTokenStore(t *testing.T) {
// ascertain that returned token equals to initial // ascertain that returned token equals to initial
require.Equal(t, pToken, res) require.Equal(t, pToken, res)
} }
func TestMapTokenStore_RemoveExpired(t *testing.T) {
// create some epoch number
e1 := uint64(1)
// create private token that expires after e1
tok1, err := NewPrivateToken(e1)
require.NoError(t, err)
// create some greater than e1 epoch number
e2 := e1 + 1
// create private token that expires after e2
tok2, err := NewPrivateToken(e2)
require.NoError(t, err)
// create token store instance
s := NewMapTokenStore()
// create storage keys for tokens
id1, err := refs.NewUUID()
require.NoError(t, err)
id2, err := refs.NewUUID()
require.NoError(t, err)
assertPresence := func(ids ...TokenID) {
for i := range ids {
_, err = s.Fetch(ids[i])
require.NoError(t, err)
}
}
assertAbsence := func(ids ...TokenID) {
for i := range ids {
_, err = s.Fetch(ids[i])
require.EqualError(t, err, ErrPrivateTokenNotFound.Error())
}
}
// store both tokens
require.NoError(t, s.Store(id1, tok1))
require.NoError(t, s.Store(id2, tok2))
// ascertain that both tokens are available
assertPresence(id1, id2)
// perform cleaning for epoch in which both tokens are not expired
require.NoError(t, s.RemoveExpired(e1))
// ascertain that both tokens are still available
assertPresence(id1, id2)
// perform cleaning for epoch greater than e1 and not greater than e2
require.NoError(t, s.RemoveExpired(e1+1))
// ascertain that tok1 was removed
assertAbsence(id1)
// ascertain that tok2 was not removed
assertPresence(id2)
}

View file

@ -17,6 +17,9 @@ type PrivateToken interface {
// Resulting signature must be verified by crypto.Verify function // Resulting signature must be verified by crypto.Verify function
// with the session public key. // with the session public key.
Sign([]byte) ([]byte, error) Sign([]byte) ([]byte, error)
// Expired must return true if and only if private token is expired in the given epoch number.
Expired(uint64) bool
} }
// PrivateTokenSource is an interface of private token storage with read access. // PrivateTokenSource is an interface of private token storage with read access.
@ -27,9 +30,16 @@ type PrivateTokenSource interface {
Fetch(TokenID) (PrivateToken, error) Fetch(TokenID) (PrivateToken, error)
} }
// EpochLifetimeStore is an interface of the storage of elements that lifetime is limited by NeoFS epoch.
type EpochLifetimeStore interface {
// RemoveExpired must remove all elements that are expired in the given epoch.
RemoveExpired(uint64) error
}
// PrivateTokenStore is an interface of the storage of private tokens addressable by TokenID. // PrivateTokenStore is an interface of the storage of private tokens addressable by TokenID.
type PrivateTokenStore interface { type PrivateTokenStore interface {
PrivateTokenSource PrivateTokenSource
EpochLifetimeStore
// Store must save passed private token in the storage under the given key. // Store must save passed private token in the storage under the given key.
// //