From 74e917810a306e361e9809cc421303ba28f4c69c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 10 Jun 2020 20:22:34 +0300 Subject: [PATCH] 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. --- accounting/sign_test.go | 14 +-- bootstrap/sign_test.go | 14 +-- container/sign_test.go | 14 +-- object/sign_test.go | 14 +-- service/errors.go | 16 +-- service/sign.go | 225 +++++++++++++++++++++++++++++++++------- service/sign_test.go | 26 ++--- service/types.go | 16 +-- service/verify_test.go | 6 +- session/create.go | 2 +- session/create_test.go | 2 +- state/sign_test.go | 14 +-- 12 files changed, 260 insertions(+), 103 deletions(-) diff --git a/accounting/sign_test.go b/accounting/sign_test.go index dd7a819f..ebc683bf 100644 --- a/accounting/sign_test.go +++ b/accounting/sign_test.go @@ -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)) } } } diff --git a/bootstrap/sign_test.go b/bootstrap/sign_test.go index 2c76117a..38121305 100644 --- a/bootstrap/sign_test.go +++ b/bootstrap/sign_test.go @@ -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)) } } } diff --git a/container/sign_test.go b/container/sign_test.go index e469399c..d9b7d26e 100644 --- a/container/sign_test.go +++ b/container/sign_test.go @@ -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)) } } } diff --git a/object/sign_test.go b/object/sign_test.go index 9480fda7..dcfbd7e3 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -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)) } } } diff --git a/service/errors.go b/service/errors.go index f3a0dfc0..e1c49002 100644 --- a/service/errors.go +++ b/service/errors.go @@ -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") diff --git a/service/sign.go b/service/sign.go index 5b1548f0..eb1c16d9 100644 --- a/service/sign.go +++ b/service/sign.go @@ -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 } diff --git a/service/sign_test.go b/service/sign_test.go index 5cb7c409..ca469b8a 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -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)) } diff --git a/service/types.go b/service/types.go index 66582f56..257b0ca5 100644 --- a/service/types.go +++ b/service/types.go @@ -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 } diff --git a/service/verify_test.go b/service/verify_test.go index c6e4d619..e13f3162 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -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)) } } diff --git a/session/create.go b/session/create.go index 35d05400..412d1fd3 100644 --- a/session/create.go +++ b/session/create.go @@ -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 } diff --git a/session/create_test.go b/session/create_test.go index 732d4fd6..943c5daa 100644 --- a/session/create_test.go +++ b/session/create_test.go @@ -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}, diff --git a/state/sign_test.go b/state/sign_test.go index 9b2bca90..05af6548 100644 --- a/state/sign_test.go +++ b/state/sign_test.go @@ -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)) } } }