From 82ffde253b2f117969ce2b17775a37e1b3a689c5 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 28 Apr 2020 19:03:15 +0300 Subject: [PATCH 1/2] service: implement Sign/Verify functions for SessionToken --- service/token.go | 87 +++++++++++++++++++++++++ service/token_test.go | 144 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) diff --git a/service/token.go b/service/token.go index 71ea6554..b6d64355 100644 --- a/service/token.go +++ b/service/token.go @@ -1,7 +1,12 @@ package service import ( + "crypto/ecdsa" + "encoding/binary" + + "github.com/nspcc-dev/neofs-api-go/internal" "github.com/nspcc-dev/neofs-api-go/refs" + crypto "github.com/nspcc-dev/neofs-crypto" ) // VerbContainer is an interface of the container of a token verb value. @@ -57,8 +62,13 @@ type SessionToken interface { SignatureContainer } +// ErrEmptyToken is raised when passed Token is nil. +const ErrEmptyToken = internal.Error("token is empty") + var _ SessionToken = (*Token)(nil) +var tokenEndianness = binary.BigEndian + // GetID is an ID field getter. func (m Token_Info) GetID() TokenID { return m.ID @@ -123,3 +133,80 @@ func (m *Token_Info) SetSessionKey(key []byte) { func (m *Token) SetSignature(sig []byte) { m.Signature = sig } + +// Returns byte slice that is used for creation/verification of the token signature. +func verificationTokenData(token SessionToken) []byte { + var sz int + + id := token.GetID() + sz += id.Size() + + ownerID := token.GetOwnerID() + sz += ownerID.Size() + + verb := uint32(token.GetVerb()) + sz += 4 + + addr := token.GetAddress() + sz += addr.CID.Size() + addr.ObjectID.Size() + + cEpoch := token.CreationEpoch() + sz += 8 + + fEpoch := token.ExpirationEpoch() + sz += 8 + + key := token.GetSessionKey() + sz += len(key) + + data := make([]byte, sz) + + var off int + + tokenEndianness.PutUint32(data, verb) + off += 4 + + tokenEndianness.PutUint64(data[off:], cEpoch) + off += 8 + + tokenEndianness.PutUint64(data[off:], fEpoch) + off += 8 + + off += copy(data[off:], id.Bytes()) + off += copy(data[off:], ownerID.Bytes()) + off += copy(data[off:], addr.CID.Bytes()) + off += copy(data[off:], addr.ObjectID.Bytes()) + off += copy(data[off:], key) + + return data +} + +// SignToken calculates and stores the signature of token information. +// +// If passed token is nil, ErrEmptyToken returns. +// If passed private key is nil, crypto.ErrEmptyPrivateKey returns. +func SignToken(token SessionToken, key *ecdsa.PrivateKey) error { + if token == nil { + return ErrEmptyToken + } else if key == nil { + return crypto.ErrEmptyPrivateKey + } + + sig, err := crypto.Sign(key, verificationTokenData(token)) + if err != nil { + return err + } + + token.SetSignature(sig) + + return nil +} + +// VerifyTokenSignature checks if token was signed correctly. +func VerifyTokenSignature(token SessionToken, key *ecdsa.PublicKey) error { + return crypto.Verify( + key, + verificationTokenData(token), + token.GetSignature(), + ) +} diff --git a/service/token_test.go b/service/token_test.go index 1a554061..bd9c0b0f 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/refs" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-crypto/test" "github.com/stretchr/testify/require" ) @@ -86,3 +88,145 @@ func TestTokenGettersSetters(t *testing.T) { require.Equal(t, sig, tok.GetSignature()) } } + +func TestSignToken(t *testing.T) { + // nil token + require.EqualError(t, + SignToken(nil, nil), + ErrEmptyToken.Error(), + ) + + var token SessionToken = new(Token) + + // nil key + require.EqualError(t, + SignToken(token, nil), + crypto.ErrEmptyPrivateKey.Error(), + ) + + // create private key for signing + sk := test.DecodeKey(0) + pk := &sk.PublicKey + + id := TokenID{} + _, err := rand.Read(id[:]) + require.NoError(t, err) + token.SetID(id) + + ownerID := OwnerID{} + _, err = rand.Read(ownerID[:]) + require.NoError(t, err) + token.SetOwnerID(ownerID) + + verb := Token_Info_Verb(1) + token.SetVerb(verb) + + addr := Address{} + _, err = rand.Read(addr.ObjectID[:]) + require.NoError(t, err) + _, err = rand.Read(addr.CID[:]) + require.NoError(t, err) + token.SetAddress(addr) + + cEpoch := uint64(1) + token.SetCreationEpoch(cEpoch) + + fEpoch := uint64(2) + token.SetExpirationEpoch(fEpoch) + + sessionKey := make([]byte, 10) + _, err = rand.Read(sessionKey[:]) + require.NoError(t, err) + token.SetSessionKey(sessionKey) + + // sign and verify token + require.NoError(t, SignToken(token, sk)) + require.NoError(t, VerifyTokenSignature(token, pk)) + + items := []struct { + corrupt func() + restore func() + }{ + { // ID + corrupt: func() { + id[0]++ + token.SetID(id) + }, + restore: func() { + id[0]-- + token.SetID(id) + }, + }, + { // Owner ID + corrupt: func() { + ownerID[0]++ + token.SetOwnerID(ownerID) + }, + restore: func() { + ownerID[0]-- + token.SetOwnerID(ownerID) + }, + }, + { // Verb + corrupt: func() { + token.SetVerb(verb + 1) + }, + restore: func() { + token.SetVerb(verb) + }, + }, + { // ObjectID + corrupt: func() { + addr.ObjectID[0]++ + token.SetAddress(addr) + }, + restore: func() { + addr.ObjectID[0]-- + token.SetAddress(addr) + }, + }, + { // CID + corrupt: func() { + addr.CID[0]++ + token.SetAddress(addr) + }, + restore: func() { + addr.CID[0]-- + token.SetAddress(addr) + }, + }, + { // Creation epoch + corrupt: func() { + token.SetCreationEpoch(cEpoch + 1) + }, + restore: func() { + token.SetCreationEpoch(cEpoch) + }, + }, + { // Expiration epoch + corrupt: func() { + token.SetExpirationEpoch(fEpoch + 1) + }, + restore: func() { + token.SetExpirationEpoch(fEpoch) + }, + }, + { // Session key + corrupt: func() { + sessionKey[0]++ + token.SetSessionKey(sessionKey) + }, + restore: func() { + sessionKey[0]-- + token.SetSessionKey(sessionKey) + }, + }, + } + + for _, v := range items { + v.corrupt() + require.Error(t, VerifyTokenSignature(token, pk)) + v.restore() + require.NoError(t, VerifyTokenSignature(token, pk)) + } +} From cce6566f1e48a22f8c15c0fb348cb8c19516ac0a Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 29 Apr 2020 10:57:07 +0300 Subject: [PATCH 2/2] service: prevent NPE in VerifyTokenSignature function This commit adds next changes to VerifyTokenSignature: * returns ErrEmptyToken on nil token argument; * returns ErrEmptyPublicKey on nil public key argument. --- service/token.go | 9 +++++++++ service/token_test.go | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/service/token.go b/service/token.go index b6d64355..077e672b 100644 --- a/service/token.go +++ b/service/token.go @@ -203,7 +203,16 @@ func SignToken(token SessionToken, key *ecdsa.PrivateKey) error { } // VerifyTokenSignature checks if token was signed correctly. +// +// If passed token is nil, ErrEmptyToken returns. +// If passed public key is nil, crypto.ErrEmptyPublicKey returns. func VerifyTokenSignature(token SessionToken, key *ecdsa.PublicKey) error { + if token == nil { + return ErrEmptyToken + } else if key == nil { + return crypto.ErrEmptyPublicKey + } + return crypto.Verify( key, verificationTokenData(token), diff --git a/service/token_test.go b/service/token_test.go index bd9c0b0f..0b28084e 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -96,6 +96,11 @@ func TestSignToken(t *testing.T) { ErrEmptyToken.Error(), ) + require.EqualError(t, + VerifyTokenSignature(nil, nil), + ErrEmptyToken.Error(), + ) + var token SessionToken = new(Token) // nil key @@ -104,6 +109,11 @@ func TestSignToken(t *testing.T) { crypto.ErrEmptyPrivateKey.Error(), ) + require.EqualError(t, + VerifyTokenSignature(token, nil), + crypto.ErrEmptyPublicKey.Error(), + ) + // create private key for signing sk := test.DecodeKey(0) pk := &sk.PublicKey