service: support broken apart signable payload of the requests

In previous implementation service package provided types and functions
that wrapped signing/verification of data with session token.
This allowed us to use these functions for signing / verification of
service requests of other packages. To support the expansion of messages
with additional parts that need to be signed, you must be able to easily
expand the signed data with new parts.

To achieve the described goal, this commit makes the following changes:

  * adds GroupSignedPayloads and GroupVerifyPayloads functions;

  * renames SignedDataWithToken to RequestData, DataWithTokenSignAccumulator
    to RequestSignedData, DataWithTokenSignSource to RequestVerifyData;

  * renames SignDataWithSessionToken/VerifyAccumulatedSignaturesWithToken
    function to SignRequestData/VerifyRequestData and makes it to use
    GroupSignedPayloads/GroupVerifyPayloads internally.
This commit is contained in:
Leonard Lyubich 2020-06-10 20:22:34 +03:00
parent 8dbd65132d
commit 74e917810a
12 changed files with 260 additions and 103 deletions

View file

@ -13,7 +13,7 @@ func TestSignBalanceRequest(t *testing.T) {
sk := test.DecodeKey(0)
type sigType interface {
service.SignedDataWithToken
service.RequestData
service.SignKeyPairAccumulator
service.SignKeyPairSource
SetToken(*service.Token)
@ -159,26 +159,26 @@ func TestSignBalanceRequest(t *testing.T) {
token := new(service.Token)
v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
{ // payload corruptions
for _, corruption := range item.payloadCorrupt {
v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
}
}

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0)
type sigType interface {
service.SignedDataWithToken
service.RequestData
service.SignKeyPairAccumulator
service.SignKeyPairSource
SetToken(*service.Token)
@ -56,26 +56,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token)
v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
{ // payload corruptions
for _, corruption := range item.payloadCorrupt {
v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
}
}

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0)
type sigType interface {
service.SignedDataWithToken
service.RequestData
service.SignKeyPairAccumulator
service.SignKeyPairSource
SetToken(*service.Token)
@ -117,26 +117,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token)
v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
{ // payload corruptions
for _, corruption := range item.payloadCorrupt {
v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
}
}

View file

@ -13,7 +13,7 @@ func TestSignVerifyRequests(t *testing.T) {
sk := test.DecodeKey(0)
type sigType interface {
service.SignedDataWithToken
service.RequestData
service.SignKeyPairAccumulator
service.SignKeyPairSource
SetToken(*Token)
@ -164,26 +164,26 @@ func TestSignVerifyRequests(t *testing.T) {
token := new(Token)
v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
{ // payload corruptions
for _, corruption := range item.payloadCorrupt {
v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
}
}

View file

@ -36,14 +36,18 @@ const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
// negative length for slice allocation.
const ErrNegativeLength = internal.Error("negative slice length")
// ErrNilDataWithTokenSignAccumulator is returned by functions that expect
// a non-nil DataWithTokenSignAccumulator, but received nil.
const ErrNilDataWithTokenSignAccumulator = internal.Error("signed data with token is nil")
// ErrNilRequestSignedData is returned by functions that expect
// a non-nil RequestSignedData, but received nil.
const ErrNilRequestSignedData = internal.Error("request signed data is nil")
// ErrNilSignatureKeySourceWithToken is returned by functions that expect
// a non-nil SignatureKeySourceWithToken, but received nil.
const ErrNilSignatureKeySourceWithToken = internal.Error("key-signature source with token is nil")
// ErrNilRequestVerifyData is returned by functions that expect
// a non-nil RequestVerifyData, but received nil.
const ErrNilRequestVerifyData = internal.Error("request verification data is nil")
// ErrNilSignedDataReader is returned by functions that expect
// a non-nil SignedDataReader, but received nil.
const ErrNilSignedDataReader = internal.Error("signed data reader is nil")
// ErrNilSignKeyPairAccumulator is returned by functions that expect
// a non-nil SignKeyPairAccumulator, but received nil.
const ErrNilSignKeyPairAccumulator = internal.Error("signature-key pair accumulator is nil")

View file

@ -2,9 +2,11 @@ package service
import (
"crypto/ecdsa"
"io"
"sync"
crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/pkg/errors"
)
type keySign struct {
@ -12,6 +14,20 @@ type keySign struct {
sign []byte
}
type signSourceGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
sources []SignedDataSource
}
type signReadersGroup struct {
SignKeyPairSource
SignKeyPairAccumulator
readers []SignedDataReader
}
var bytesPool = sync.Pool{
New: func() interface{} {
return make([]byte, 5<<20)
@ -176,54 +192,191 @@ func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error {
)
}
// SignDataWithSessionToken calculates data with token signature and adds it to accumulator.
// SignRequestData calculates request data signature and adds it to accumulator.
//
// Any change of data or session token info provoke signature breakdown.
// Any change of request data provoke signature breakdown.
//
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
// If passed DataWithTokenSignAccumulator is nil, ErrNilDataWithTokenSignAccumulator returns.
func SignDataWithSessionToken(key *ecdsa.PrivateKey, src DataWithTokenSignAccumulator) error {
// If passed RequestSignedData is nil, ErrNilRequestSignedData returns.
func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error {
if src == nil {
return ErrNilDataWithTokenSignAccumulator
} else if r, ok := src.(SignedDataReader); ok {
return AddSignatureWithKey(key, &signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairAccumulator: src,
rdr: r,
token: src.GetSessionToken(),
},
)
return ErrNilRequestSignedData
}
return AddSignatureWithKey(key, &signAccumWithToken{
SignedDataSource: src,
SignKeyPairAccumulator: src,
sigSrc, err := GroupSignedPayloads(
src,
src,
NewSignedSessionToken(
src.GetSessionToken(),
),
)
if err != nil {
return err
}
token: src.GetSessionToken(),
})
return AddSignatureWithKey(key, sigSrc)
}
// VerifyAccumulatedSignaturesWithToken checks if accumulated key-signature pairs of data with token are valid.
// VerifyRequestData checks if accumulated key-signature pairs of data with token are valid.
//
// If passed DataWithTokenSignSource is nil, ErrNilSignatureKeySourceWithToken returns.
func VerifyAccumulatedSignaturesWithToken(src DataWithTokenSignSource) error {
// If passed RequestVerifyData is nil, ErrNilRequestVerifyData returns.
func VerifyRequestData(src RequestVerifyData) error {
if src == nil {
return ErrNilSignatureKeySourceWithToken
} else if r, ok := src.(SignedDataReader); ok {
return VerifyAccumulatedSignatures(&signDataReaderWithToken{
SignedDataSource: src,
SignKeyPairSource: src,
rdr: r,
token: src.GetSessionToken(),
})
return ErrNilRequestVerifyData
}
return VerifyAccumulatedSignatures(&signAccumWithToken{
SignedDataSource: src,
SignKeyPairSource: src,
verSrc, err := GroupVerifyPayloads(
src,
src,
NewVerifiedSessionToken(
src.GetSessionToken(),
),
)
if err != nil {
return err
}
token: src.GetSessionToken(),
})
return VerifyAccumulatedSignatures(verSrc)
}
// SignedData returns payload bytes concatenation from all sources keeping order.
func (s signSourceGroup) SignedData() ([]byte, error) {
chunks := make([][]byte, 0, len(s.sources))
sz := 0
for i := range s.sources {
data, err := s.sources[i].SignedData()
if err != nil {
return nil, errors.Wrapf(err, "could not get signed payload of element #%d", i)
}
chunks = append(chunks, data)
sz += len(data)
}
res := make([]byte, sz)
off := 0
for i := range chunks {
off += copy(res[off:], chunks[i])
}
return res, nil
}
// SignedData returns payload bytes concatenation from all readers.
func (s signReadersGroup) SignedData() ([]byte, error) {
return SignedDataFromReader(s)
}
// SignedDataSize returns the sum of sizes of all readers.
func (s signReadersGroup) SignedDataSize() (sz int) {
for i := range s.readers {
sz += s.readers[i].SignedDataSize()
}
return
}
// ReadSignedData reads data from all readers to passed buffer keeping order.
//
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
func (s signReadersGroup) ReadSignedData(p []byte) (int, error) {
sz := s.SignedDataSize()
if len(p) < sz {
return 0, io.ErrUnexpectedEOF
}
off := 0
for i := range s.readers {
n, err := s.readers[i].ReadSignedData(p[off:])
off += n
if err != nil {
return off, errors.Wrapf(err, "could not read signed payload of element #%d", i)
}
}
return off, nil
}
// GroupSignedPayloads groups SignKeyPairAccumulator and SignedDataSource list to DataWithSignKeyAccumulator.
//
// If passed SignKeyPairAccumulator is nil, ErrNilSignKeyPairAccumulator returns.
//
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
// Nil elements in list are ignored.
//
// If all elements implement SignedDataReader, result implements it too.
func GroupSignedPayloads(acc SignKeyPairAccumulator, sources ...SignedDataSource) (DataWithSignKeyAccumulator, error) {
if acc == nil {
return nil, ErrNilSignKeyPairAccumulator
}
return groupPayloads(acc, nil, sources...), nil
}
// GroupVerifyPayloads groups SignKeyPairSource and SignedDataSource list to DataWithSignKeySource.
//
// If passed SignKeyPairSource is nil, ErrNilSignatureKeySource returns.
//
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
// Nil elements in list are ignored.
//
// If all elements implement SignedDataReader, result implements it too.
func GroupVerifyPayloads(src SignKeyPairSource, sources ...SignedDataSource) (DataWithSignKeySource, error) {
if src == nil {
return nil, ErrNilSignatureKeySource
}
return groupPayloads(nil, src, sources...), nil
}
func groupPayloads(acc SignKeyPairAccumulator, src SignKeyPairSource, sources ...SignedDataSource) interface {
SignedDataSource
SignKeyPairSource
SignKeyPairAccumulator
} {
var allReaders bool
for i := range sources {
if sources[i] == nil {
continue
} else if _, allReaders = sources[i].(SignedDataReader); !allReaders {
break
}
}
if !allReaders {
res := &signSourceGroup{
SignKeyPairSource: src,
SignKeyPairAccumulator: acc,
sources: make([]SignedDataSource, 0, len(sources)),
}
for i := range sources {
if sources[i] != nil {
res.sources = append(res.sources, sources[i])
}
}
return res
}
res := &signReadersGroup{
SignKeyPairSource: src,
SignKeyPairAccumulator: acc,
readers: make([]SignedDataReader, 0, len(sources)),
}
for i := range sources {
if sources[i] != nil {
res.readers = append(res.readers, sources[i].(SignedDataReader))
}
}
return res
}

View file

@ -257,16 +257,16 @@ func TestVerifySignatureWithKey(t *testing.T) {
}
func TestSignVerifyDataWithSessionToken(t *testing.T) {
// sign with empty DataWithTokenSignAccumulator
// sign with empty RequestSignedData
require.EqualError(t,
SignDataWithSessionToken(nil, nil),
ErrNilDataWithTokenSignAccumulator.Error(),
SignRequestData(nil, nil),
ErrNilRequestSignedData.Error(),
)
// verify with empty DataWithTokenSignSource
// verify with empty RequestVerifyData
require.EqualError(t,
VerifyAccumulatedSignaturesWithToken(nil),
ErrNilSignatureKeySourceWithToken.Error(),
VerifyRequestData(nil),
ErrNilRequestVerifyData.Error(),
)
// create test session token
@ -287,16 +287,16 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
sk := test.DecodeKey(0)
// sign with private key
require.NoError(t, SignDataWithSessionToken(sk, src))
require.NoError(t, SignRequestData(sk, src))
// ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src))
require.NoError(t, VerifyRequestData(src))
// break the data
src.data[0]++
// ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src))
require.Error(t, VerifyRequestData(src))
// restore the data
src.data[0]--
@ -305,13 +305,13 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
token.SetVerb(initVerb + 1)
// ascertain that verification is failed
require.Error(t, VerifyAccumulatedSignaturesWithToken(src))
require.Error(t, VerifyRequestData(src))
// restore the token
token.SetVerb(initVerb)
// ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(src))
require.NoError(t, VerifyRequestData(src))
// wrap to data reader
rdr := &testSignedDataReader{
@ -319,8 +319,8 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) {
}
// sign with private key
require.NoError(t, SignDataWithSessionToken(sk, rdr))
require.NoError(t, SignRequestData(sk, rdr))
// ascertain that verification is passed
require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr))
require.NoError(t, VerifyRequestData(rdr))
}

View file

@ -250,20 +250,20 @@ type DataWithSignKeySource interface {
SignKeyPairSource
}
// SignedDataWithToken is an interface of data-token pair with read access.
type SignedDataWithToken interface {
// RequestData is an interface of the request information with read access.
type RequestData interface {
SignedDataSource
SessionTokenSource
}
// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access.
type DataWithTokenSignAccumulator interface {
SignedDataWithToken
// RequestSignedData is an interface of request information with signature write access.
type RequestSignedData interface {
RequestData
SignKeyPairAccumulator
}
// DataWithTokenSignSource is an interface of data-token pair with signature read access.
type DataWithTokenSignSource interface {
SignedDataWithToken
// RequestVerifyData is an interface of request information with signature read access.
type RequestVerifyData interface {
RequestData
SignKeyPairSource
}

View file

@ -69,7 +69,7 @@ func BenchmarkSignDataWithSessionToken(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
require.NoError(b, SignDataWithSessionToken(key, req))
require.NoError(b, SignRequestData(key, req))
}
}
@ -91,14 +91,14 @@ func BenchmarkVerifyAccumulatedSignaturesWithToken(b *testing.B) {
for i := 0; i < 10; i++ {
key := test.DecodeKey(i)
require.NoError(b, SignDataWithSessionToken(key, req))
require.NoError(b, SignRequestData(key, req))
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
require.NoError(b, VerifyAccumulatedSignaturesWithToken(req))
require.NoError(b, VerifyRequestData(req))
}
}

View file

@ -53,7 +53,7 @@ func (s gRPCCreator) Create(ctx context.Context, p CreateParamsSource) (CreateRe
req.SetExpirationEpoch(p.ExpirationEpoch())
// sign with private key
if err := service.SignDataWithSessionToken(s.key, req); err != nil {
if err := service.SignRequestData(s.key, req); err != nil {
return nil, err
}

View file

@ -84,7 +84,7 @@ func TestGRPCCreator_Create(t *testing.T) {
require.Equal(t, ownerID, req.GetOwnerID())
require.Equal(t, created, req.CreationEpoch())
require.Equal(t, expired, req.ExpirationEpoch())
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(req))
require.NoError(t, service.VerifyRequestData(req))
},
resp: &CreateResponse{
ID: TokenID{1, 2, 3},

View file

@ -12,7 +12,7 @@ func TestRequestSign(t *testing.T) {
sk := test.DecodeKey(0)
type sigType interface {
service.SignedDataWithToken
service.RequestData
service.SignKeyPairAccumulator
service.SignKeyPairSource
SetToken(*service.Token)
@ -68,26 +68,26 @@ func TestRequestSign(t *testing.T) {
token := new(service.Token)
v.SetToken(token)
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
token.SetSessionKey(append(token.GetSessionKey(), 1))
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
{ // payload corruptions
for _, corruption := range item.payloadCorrupt {
v := item.constructor()
require.NoError(t, service.SignDataWithSessionToken(sk, v))
require.NoError(t, service.SignRequestData(sk, v))
require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.NoError(t, service.VerifyRequestData(v))
corruption(v)
require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v))
require.Error(t, service.VerifyRequestData(v))
}
}
}