From 74e917810a306e361e9809cc421303ba28f4c69c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 10 Jun 2020 20:22:34 +0300 Subject: [PATCH 01/11] 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 dd7a819..ebc683b 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 2c76117..3812130 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 e469399..d9b7d26 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 9480fda..dcfbd7e 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 f3a0dfc..e1c4900 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 5b1548f..eb1c16d 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 5cb7c40..ca469b8 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 66582f5..257b0ca 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 c6e4d61..e13f316 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 35d0540..412d1fd 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 732d4fd..943c5da 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 9b2bca9..05af654 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)) } } } From 55c3b714c0cd6032011932162fbfd0cfe3281f55 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 14:25:19 +0300 Subject: [PATCH 02/11] service: define BearerToken interface --- service/bearer.go | 96 +++++++++++++++++++++++ service/bearer_test.go | 172 +++++++++++++++++++++++++++++++++++++++++ service/types.go | 34 ++++++++ 3 files changed, 302 insertions(+) create mode 100644 service/bearer.go create mode 100644 service/bearer_test.go diff --git a/service/bearer.go b/service/bearer.go new file mode 100644 index 0000000..bc8aaa5 --- /dev/null +++ b/service/bearer.go @@ -0,0 +1,96 @@ +package service + +import ( + "crypto/ecdsa" + "io" + + "github.com/nspcc-dev/neofs-api-go/refs" + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type signedBearerToken struct { + BearerToken +} + +const fixedBearerTokenDataSize = 0 + + refs.OwnerIDSize + + 8 + +// NewSignedBearerToken wraps passed BearerToken in a component suitable for signing. +// +// Result can be used in AddSignatureWithKey function. +func NewSignedBearerToken(token BearerToken) DataWithSignKeyAccumulator { + return &signedBearerToken{ + BearerToken: token, + } +} + +// NewVerifiedBearerToken wraps passed SessionToken in a component suitable for signature verification. +// +// Result can be used in VerifySignatureWithKey function. +func NewVerifiedBearerToken(token BearerToken) DataWithSignature { + return &signedBearerToken{ + BearerToken: token, + } +} + +// AddSignKey calls a Signature field setter and an OwnerKey field setter with corresponding arguments. +func (s signedBearerToken) AddSignKey(sig []byte, key *ecdsa.PublicKey) { + if s.BearerToken != nil { + s.SetSignature(sig) + + s.SetOwnerKey( + crypto.MarshalPublicKey(key), + ) + } +} + +// SignedData returns token information in a binary representation. +func (s signedBearerToken) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the length of signed token information slice. +func (s signedBearerToken) SignedDataSize() int { + return bearerTokenInfoSize(s.BearerToken) +} + +// ReadSignedData copies a binary representation of the token information to passed buffer. +// +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (s signedBearerToken) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + copyBearerTokenSignedData(p, s.BearerToken) + + return sz, nil +} + +func bearerTokenInfoSize(v ACLRulesSource) int { + if v == nil { + return 0 + } + return fixedBearerTokenDataSize + len(v.GetACLRules()) +} + +// Fills passed buffer with signing token information bytes. +// Does not check buffer length, it is understood that enough space is allocated in it. +// +// If passed BearerTokenInfo, buffer remains unchanged. +func copyBearerTokenSignedData(buf []byte, token BearerTokenInfo) { + if token == nil { + return + } + + var off int + + off += copy(buf[off:], token.GetACLRules()) + + off += copy(buf[off:], token.GetOwnerID().Bytes()) + + tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch()) + off += 8 +} diff --git a/service/bearer_test.go b/service/bearer_test.go new file mode 100644 index 0000000..da359f2 --- /dev/null +++ b/service/bearer_test.go @@ -0,0 +1,172 @@ +package service + +import ( + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +type testBearerToken struct { + aclRules []byte + expEpoch uint64 + owner OwnerID + key []byte + sig []byte +} + +func (s testBearerToken) GetACLRules() []byte { + return s.aclRules +} + +func (s *testBearerToken) SetACLRules(v []byte) { + s.aclRules = v +} + +func (s testBearerToken) ExpirationEpoch() uint64 { + return s.expEpoch +} + +func (s *testBearerToken) SetExpirationEpoch(v uint64) { + s.expEpoch = v +} + +func (s testBearerToken) GetOwnerID() OwnerID { + return s.owner +} + +func (s *testBearerToken) SetOwnerID(v OwnerID) { + s.owner = v +} + +func (s testBearerToken) GetOwnerKey() []byte { + return s.key +} + +func (s *testBearerToken) SetOwnerKey(v []byte) { + s.key = v +} + +func (s testBearerToken) GetSignature() []byte { + return s.sig +} + +func (s *testBearerToken) SetSignature(v []byte) { + s.sig = v +} + +func TestBearerTokenMsgGettersSetters(t *testing.T) { + var tok BearerToken = new(testBearerToken) + + { // ACLRules + rules := []byte{1, 2, 3} + + tok.SetACLRules(rules) + + require.Equal(t, rules, tok.GetACLRules()) + } + + { // OwnerID + ownerID := OwnerID{} + _, err := rand.Read(ownerID[:]) + require.NoError(t, err) + + tok.SetOwnerID(ownerID) + + require.Equal(t, ownerID, tok.GetOwnerID()) + } + + { // ValidUntil + e := uint64(5) + + tok.SetExpirationEpoch(e) + + require.Equal(t, e, tok.ExpirationEpoch()) + } + + { // OwnerKey + key := make([]byte, 10) + _, err := rand.Read(key) + require.NoError(t, err) + + tok.SetOwnerKey(key) + + require.Equal(t, key, tok.GetOwnerKey()) + } + + { // Signature + sig := make([]byte, 10) + _, err := rand.Read(sig) + require.NoError(t, err) + + tok.SetSignature(sig) + + require.Equal(t, sig, tok.GetSignature()) + } +} + +func TestSignVerifyBearerToken(t *testing.T) { + var token BearerToken = new(testBearerToken) + + // create private key for signing + sk := test.DecodeKey(0) + pk := &sk.PublicKey + + rules := []byte{1, 2, 3} + token.SetACLRules(rules) + + ownerID := OwnerID{} + _, err := rand.Read(ownerID[:]) + require.NoError(t, err) + token.SetOwnerID(ownerID) + + fEpoch := uint64(2) + token.SetExpirationEpoch(fEpoch) + + signedToken := NewSignedBearerToken(token) + verifiedToken := NewVerifiedBearerToken(token) + + // sign and verify token + require.NoError(t, AddSignatureWithKey(sk, signedToken)) + require.NoError(t, VerifySignatureWithKey(pk, verifiedToken)) + + items := []struct { + corrupt func() + restore func() + }{ + { // ACLRules + corrupt: func() { + token.SetACLRules(append(rules, 1)) + }, + restore: func() { + token.SetACLRules(rules) + }, + }, + { // Owner ID + corrupt: func() { + ownerID[0]++ + token.SetOwnerID(ownerID) + }, + restore: func() { + ownerID[0]-- + token.SetOwnerID(ownerID) + }, + }, + { // Expiration epoch + corrupt: func() { + token.SetExpirationEpoch(fEpoch + 1) + }, + restore: func() { + token.SetExpirationEpoch(fEpoch) + }, + }, + } + + for _, v := range items { + v.corrupt() + require.Error(t, VerifySignatureWithKey(pk, verifiedToken)) + v.restore() + require.NoError(t, VerifySignatureWithKey(pk, verifiedToken)) + } +} diff --git a/service/types.go b/service/types.go index 257b0ca..dada0c8 100644 --- a/service/types.go +++ b/service/types.go @@ -267,3 +267,37 @@ type RequestVerifyData interface { RequestData SignKeyPairSource } + +// ACLRulesSource is an interface of the container of binary extended ACL rules with read access. +type ACLRulesSource interface { + GetACLRules() []byte +} + +// ACLRulesContainer is an interface of the container of binary extended ACL rules. +type ACLRulesContainer interface { + ACLRulesSource + SetACLRules([]byte) +} + +// BearerTokenInfo is an interface of a fixed set of Bearer token information value containers. +// Contains: +// - binary extended ACL rules; +// - expiration epoch number; +// - ID of the token's owner. +type BearerTokenInfo interface { + ACLRulesContainer + ExpirationEpochContainer + OwnerIDContainer +} + +// BearerToken is an interface of Bearer token information and key-signature pair. +type BearerToken interface { + BearerTokenInfo + OwnerKeyContainer + SignatureContainer +} + +// BearerTokenSource is an interface of the container of a BearerToken with read access. +type BearerTokenSource interface { + GetBearerToken() BearerToken +} From 705582dbc7084c41a87aafd7d3d5d8427aff74ea Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 14:35:14 +0300 Subject: [PATCH 03/11] service: define request X-headers interface --- service/meta.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ service/types.go | 11 +++++++++ 2 files changed, 69 insertions(+) diff --git a/service/meta.go b/service/meta.go index 3f01758..f1b4613 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,5 +1,13 @@ package service +import ( + "io" +) + +type extHdrSrcWrapper struct { + extHdrSrc ExtendedHeadersSource +} + // CutMeta returns current value and sets RequestMetaHeader to empty value. func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { cp := *m @@ -11,3 +19,53 @@ func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v } + +// ExtendedHeadersSignedData wraps passed ExtendedHeadersSource and returns SignedDataSource. +func ExtendedHeadersSignedData(headers ExtendedHeadersSource) SignedDataSource { + return &extHdrSrcWrapper{ + extHdrSrc: headers, + } +} + +// SignedData returns extended headers in a binary representation. +func (s extHdrSrcWrapper) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the length of extended headers slice. +func (s extHdrSrcWrapper) SignedDataSize() (res int) { + if s.extHdrSrc != nil { + for _, h := range s.extHdrSrc.ExtendedHeaders() { + if h != nil { + res += len(h.Key()) + len(h.Value()) + } + } + } + + return +} + +// ReadSignedData copies a binary representation of the extended headers to passed buffer. +// +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (s extHdrSrcWrapper) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + if s.extHdrSrc != nil { + off := 0 + for _, h := range s.extHdrSrc.ExtendedHeaders() { + if h == nil { + continue + } + + off += copy(p[off:], []byte(h.Key())) + + off += copy(p[off:], []byte(h.Value())) + } + } + + return sz, nil +} diff --git a/service/types.go b/service/types.go index dada0c8..87c3a77 100644 --- a/service/types.go +++ b/service/types.go @@ -301,3 +301,14 @@ type BearerToken interface { type BearerTokenSource interface { GetBearerToken() BearerToken } + +// ExtendedHeader is an interface of string key-value pair with read access. +type ExtendedHeader interface { + Key() string + Value() string +} + +// ExtendedHeadersSource is an interface of ExtendedHeader list with read access. +type ExtendedHeadersSource interface { + ExtendedHeaders() []ExtendedHeader +} From ee584f325caa9652a8a30d7455577c59d4c08fee Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 15:01:25 +0300 Subject: [PATCH 04/11] Update to neofs-api v1.1.0 --- Makefile | 2 +- container/service.pb.go | Bin 60767 -> 97427 bytes container/service.proto | 45 ++++++++++++++++++++ container/types.go | 20 +++++++++ container/types_test.go | 20 +++++++++ docs/container.md | 90 ++++++++++++++++++++++++++++++++++++++++ docs/service.md | 55 ++++++++++++++++++++++++ service/bearer.go | 30 ++++++++++++++ service/bearer_test.go | 24 +++++++++++ service/meta.go | 15 +++++++ service/meta.pb.go | Bin 15863 -> 27975 bytes service/meta.proto | 17 ++++++++ service/meta_test.go | 28 +++++++++++++ service/verify.go | 5 +++ service/verify.pb.go | Bin 40916 -> 55921 bytes service/verify.proto | 26 ++++++++++++ service/verify_test.go | 13 ++++++ 17 files changed, 389 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3dcd690..159c7e3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PROTO_VERSION=v1.0.0 +PROTO_VERSION=v1.1.0 PROTO_URL=https://github.com/nspcc-dev/neofs-api/archive/$(PROTO_VERSION).tar.gz B=\033[0;1m diff --git a/container/service.pb.go b/container/service.pb.go index 61312544df1ea6372eb29365f4944017c1401917..f49f79fe3cf389e9a31cf20aa425ee4e7ded81e1 100644 GIT binary patch delta 10579 zcmb_iTWlQF8J3-dVh7tX$R-YUGInluaO|1AubTi?LW2`G#1KMB2zYjP7HsUbvtA3E zkStLf6;(nd995+)sz69oX_W%ns;wg0g6Kmpk+`WsiWDHKR5els=>wvu5A>YxJGN)e z*a1Xe#T`DNnM6)&BSwwGonJ?qw)l2^!ix&9r4d%Rg|s#KiLmaL<- zHNCx-m~6Xw*}zUqd{UPbz4eyZSO5L`-~lmiwQLEJjvlj))#j%QS*vs0GSguFwT#7V>xSTZn3sze&178^R|FEZ>l@eJuzA8F1X{~ z_W6B=@Oioz-_R{q+7C4tUJ@5u)@(Is-jc9UeicI-ZwYZZvS!^XlZ%khS({!8ItY=F z;0f^>TJ_ry&1#K?X!{y(2+^M1Y=>w=(HldwhRz$q`kq?fD(=0ero%L(%i86YRD3$S zEQVeT*R}0Zl-inOwKYXgMzUZPM#f~CEShdG7HsAN65!Q4PhNx}YQvpUgN5=S`mb&J zx~dU#fPP5cmnMRU9Vz4|isOK`#O=aic}_Iex0U5zjh1e2JmckL<=HN2bGTKv1S_*? zPL$`*ilt=mNH9Tyh&`}bZV{)N zzF{)D(6rGcy;gH~n_~xXYw&rbeNR)6q@@d4`IcCb1#pQKRR@FI#D~`>OwwB`9h2m) zHc3zQcAA?~Ezg*wx{d87scKELNm|#i%Os7i*<_M>oNu)`&Xt_?--bc+(C$@#FiF=m z_L-!C#txHoqBUufelHt~;l0PAAyQ9U_ex(1{6F%i0EJs)3ve3=gu>YINc-MveX96D8{9d!qCmU&iUMtL1)w!Z3bY1Efi_4|pbb*Q zPwSc!s_B0Dnj`A!+dCGT@Iqyiw7Ie(Z^eu2TEuWm$rN0-aYMk(&c>F&ZfUwKHG-`?ki@E+RP;2M#&YUD7sYAX_g!%nK5zQBW_8D3v*N( zhD2dVn)Wl5T#sSEIU+p?esVzqCyjd=ZDS9e9tXc9_+@aPf?fn&2IhD$Crcy|C23ft z4wNM*VLCw+#eEDK69683n1U8b(6g|Fe%8FPM`np~QU+Ty#1rHYB|1&1Fb=qKkCac~ z*wC9vndC9p%%C$!GLlL{%BK=xm4~5p01aW`$S@?>ABWrGL>SCeStaEs`GY=WRU+P* zV;<}wN+VPf4sgLc&rsr;0!3>9nJj(;V;VS@Zc`?(ilv?IvI;JI62W>B_rxI~>wunv zl393_uA-bg?D3!%ONK)yF{%luO0TkTFmZECoFwGi(4JL^r8bUZEV2YNs~cnJus0;2 z5?w{q5hdCjBF1M9(<%xRm`SqSDEqUJPup0)C5DujT&Uq8i)?UWPhkntSpqHCS-77V z&d@P%9*bBK_d0}02#ChZ=qAi4hJgi-g#eP72N^1fJq}iLJO!*W_t_^{tYzm=Cg`RC zquE$!0uMxO$d58gkid4u43wdi?nzMsSa`VR;alCJ>9G{lPJ$6zF)_sapfh-7;hM2%7z3;G}?iZOi+z$tkUɛLu8qrQ-m}5i)fyu)($1Oub46aPU zxoPmzPJ}%?L6;A$2UbcF`ei9

4HI;D6t>DU zlt5`*;8e@JX8lYchRkRg3}pkei0z73He1$UK1q;88j6ultP+42Qp+=XgrT*NBXe9y z;P|d<1}(ud%OP`|uID@{p9WNrkTD$9*;t8E27t4u%ugji4RvzYny;jM&h2UBA@RW>&5lq4_cDutcU=^ifv}JP z<$%K+k1|vY2@@I=mNn+2%$VA;v>^^h6D%AgG@@~uR_PWA@3BvD=3+T!jUhhiBz82} zWtEed(5%567+JsNldzh9_z0?>P2qa>Xl4epj*elC;qb(vK^9ykfmMW#=78bAZNwIz zRTxi3k4nm66!Kwb3NiE#8w8XrO$apGADo!Ml>-KwCfh%I8Xqs|PCk`WJLaJtQQ$~9 zsw??!1`_f>Km>Xd*R1TsKHnOoef=nho$NR)@085e&1a%C_Q)QiOvrNpgtasw$@z=^ zVMnCX`4FR@z#x+&2lwdmio(hgD7(LsARnfsJ^BR#9H!G3rs*P&J$i(5Fn_U(vupDK zjTykA$gI~;V)5bA0H1Ucki!8}!{P8B~H-JTu%+ih(LGS{!myR6QI zg%$XYFJ$QaAEv)b=oV2UXueMk|3dYS!OEGTw zm@E$OPnltuw-qDq{;|2k9gy~ClG-aop3y{Vqs6=Yf&AAYp`+C~29$dNp>w=v{wsHvb(3YQ|7@ul zv9@lvj8$>!NK@^?UeP{r<9=gHS67!hymhO*=K4$hhei%t+hxCTBKUwllGi-dH~K|| zsRbox1$HPAbrgZ$U0q^kw%-iKh0(W7($C#lgH%~{^klX8G_x}7oQua>MSbpAwG$B^ zWtvQ%>s9f~(Jq6fm_7cENt&E|#3Wsw{5SwYrEU-v;%xCz|CV`OucV8?Lg`|Vlr9G8 z^5i)QpzD_OfwXdC<wU4Rtx?e?phUy> zw#fSsq9j-Xn@*U%+w%B6(+3}Xd#m{H@vFinaG_2d{?2)mP2b5q;?3_K^!r#W$(kGt z$Mu~YT1=wy#NJB76TiAgeDM5oL)FTgPh6}rXyVw}Lnf*Jg%>Kf{Ghp748L@^a{Gn9 zSA`^jQ^?_up1n6jJ9Y7A%T=mW-gxrr>elfdbuazyPEN{yVUww@ejn#90 zo}M0+uqq03d$+59m_ZGwshOkcf3G=uOg#HihZq!1;?>G63;zTKVVit)#4mBaU@VC3 z3y7WHU~Lb|_ASgS@cd)HHvj3Q^5-Z1S!G82{dYpm<|k*)n4W**$(D-!^e3SpRh>R0 zCf;2!XK5%NV(wsgMINn8y!-ms423HD{&Fd#OpgrEvxrm2n2?%Na)v qgDj+m|Eltf7TeC{1Ha!pyI*{G=3v<7CB0FpI^9=YdHo;HT=hTu&%?n0 delta 3611 zcmYk9y{aZf5Qf=3uCRIw25zz#EI7YCGglzqfS^Y6o9Ho8_`qNU@d^%VVs2od_zB*G zn3-&%iJ)jMW>!B>FK*Jz>94!qs<+;%u5C9i4E>(=JATc5o2u)5Uzyz@ir_mqF$x4bT6UJt&9BY)TI^mG1Btj(Ig zr#i1)%4?6cu?OJngyvMusfK$i-y=7=9ve6|iqPbp8DGLue6Q?Je0NpeNeu|?kpCET z04GNb%^W>fqDpMFc(MC42n)Ve;H+G(uwCGYB%-Nq|9dVI<57Cv~_Lf7c}r zi7#eDrDyw25@%C z;6`Gn!uy7pCvYabo8e-I)RDET0q8Nk;zs>HVdo`rN{SPn8E3+((fX@m*tmRvoD{$e zg7Q_d9@33+h;SMLF##}tUMlQK{eeBH-^rm|H2ZTTZ5%4fFd2lt;P9YJ1l6#!5c6n~ zz*los{yr}v?3D<3**}+{)f8h-OTGA5syTUZtAY}}s7cR;Um}l&u<=93wG4Ac!Rsql zTWnY|3m&i7s1%eZLEbqZZbxx#&%~XTB)Kz3TS7OVwff*f&6;8-@JId_uwmpA?&((t zj`g|NGt{qbC)bOgN@i}g=td=#K)5wRZFC*V4P}G^&)l0ps95!W!N$O9^IM?dV5e0|AlvY4mL;|X z!VIewM-$%`>j;dc)QsRCnsn`r8JdZ_w?KDT(0i*pFms|0L?o!qq*pQ6u+>&vwy#LE zRadDYL|1!;)F*6q!GA{ZEuL&LCWCK>g9)5czwVRn1FNNFVb1}=#O&OPmy3E+4Jgy> z*-?%YZx*cD%vRVl4GT7G5VDXk@%%dxtu`Y_dW{=pUJw&kHR_DiK#At+(^kh(Syd$^ z0eBbVR7P+JP-8`z*luys(liiD)+x-oQMWdZO`X|FPSs%BYjMM&!m~BRZ0sNC(E4zm z>rEAD*sy0i6wR@~jvjAG4#CdpYF1C9l08R()><|y=qXK(ozg!{1DNH7@Bx)K12GNG zG}7RVR@xEfPP4Ir6a6r^V{FgF=J?}q5Z#DGb5EiNh^cWy^@!H0B>G zl%O5xonDpC4uW;)5K!d7euLbN-1Z*n%m_ZtCfX<35Nv*S8p{2=$etBt4370m%<-UKmdA1pHkw}Sza8;wj%|o&|+SRegC0yMrssKd7?vNuSH!OiKULEGPj{Xf32FkZ*O0DR3>wdN@bKo z_SK|R>H7EW^x*pChd;e={r%%V?|=Wv?alS|&%SmG$&7U8?e0}e? OAKw1)pO5Z+_2Pdg1eeeN diff --git a/container/service.proto b/container/service.proto index 7df2c66..b174052 100644 --- a/container/service.proto +++ b/container/service.proto @@ -27,6 +27,12 @@ service Service { // List returns all user's containers rpc List(ListRequest) returns (ListResponse); + + // SetExtendedACL changes extended ACL rules of the container + rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse); + + // GetExtendedACL returns extended ACL rules of the container + rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse); } message PutRequest { @@ -99,3 +105,42 @@ message ListResponse { // CID (container id) is list of SHA256 hashes of the container structures repeated bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; } + +message ExtendedACLKey { + // ID (container id) is a SHA256 hash of the container structure + bytes ID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false]; +} + +message ExtendedACLValue { + // EACL carries binary representation of the table of extended ACL rules + bytes EACL = 1; + // Signature carries EACL field signature + bytes Signature = 2; +} + +message SetExtendedACLRequest { + // Key carries key to extended ACL information + ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // Value carries extended ACL information + ExtendedACLValue Value = 2 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestMetaHeader contains information about request meta headers (should be embedded into message) + service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) + service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} + +message SetExtendedACLResponse {} + +message GetExtendedACLRequest { + // Key carries key to extended ACL information + ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestMetaHeader contains information about request meta headers (should be embedded into message) + service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) + service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} + +message GetExtendedACLResponse { + // ACL carries extended ACL information + ExtendedACLValue ACL = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; +} diff --git a/container/types.go b/container/types.go index f340aa5..38bdcff 100644 --- a/container/types.go +++ b/container/types.go @@ -158,3 +158,23 @@ func (m ListRequest) GetOwnerID() OwnerID { func (m *ListRequest) SetOwnerID(owner OwnerID) { m.OwnerID = owner } + +// GetID is an ID field getter. +func (m ExtendedACLKey) GetID() CID { + return m.ID +} + +// SetID is an ID field setter. +func (m *ExtendedACLKey) SetID(v CID) { + m.ID = v +} + +// SetEACL is an EACL field setter. +func (m *ExtendedACLValue) SetEACL(v []byte) { + m.EACL = v +} + +// SetSignature is a Signature field setter. +func (m *ExtendedACLValue) SetSignature(sig []byte) { + m.Signature = sig +} diff --git a/container/types_test.go b/container/types_test.go index cc171cb..76bbe1c 100644 --- a/container/types_test.go +++ b/container/types_test.go @@ -140,3 +140,23 @@ func TestListRequestGettersSetters(t *testing.T) { require.Equal(t, owner, m.GetOwnerID()) }) } + +func TestExtendedACLKey(t *testing.T) { + s := new(ExtendedACLKey) + + id := CID{1, 2, 3} + s.SetID(id) + require.Equal(t, id, s.GetID()) +} + +func TestExtendedACLValue(t *testing.T) { + s := new(ExtendedACLValue) + + acl := []byte{1, 2, 3} + s.SetEACL(acl) + require.Equal(t, acl, s.GetEACL()) + + sig := []byte{4, 5, 6} + s.SetSignature(sig) + require.Equal(t, sig, s.GetSignature()) +} diff --git a/docs/container.md b/docs/container.md index f0188ca..fd89acd 100644 --- a/docs/container.md +++ b/docs/container.md @@ -10,12 +10,18 @@ - Messages - [DeleteRequest](#container.DeleteRequest) - [DeleteResponse](#container.DeleteResponse) + - [ExtendedACLKey](#container.ExtendedACLKey) + - [ExtendedACLValue](#container.ExtendedACLValue) + - [GetExtendedACLRequest](#container.GetExtendedACLRequest) + - [GetExtendedACLResponse](#container.GetExtendedACLResponse) - [GetRequest](#container.GetRequest) - [GetResponse](#container.GetResponse) - [ListRequest](#container.ListRequest) - [ListResponse](#container.ListResponse) - [PutRequest](#container.PutRequest) - [PutResponse](#container.PutResponse) + - [SetExtendedACLRequest](#container.SetExtendedACLRequest) + - [SetExtendedACLResponse](#container.SetExtendedACLResponse) - [container/types.proto](#container/types.proto) @@ -46,6 +52,8 @@ rpc Put(PutRequest) returns (PutResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Get(GetRequest) returns (GetResponse); rpc List(ListRequest) returns (ListResponse); +rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse); +rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse); ``` @@ -80,6 +88,20 @@ List returns all user's containers | Name | Input | Output | | ---- | ----- | ------ | | List | [ListRequest](#container.ListRequest) | [ListResponse](#container.ListResponse) | +#### Method SetExtendedACL + +SetExtendedACL changes extended ACL rules of the container + +| Name | Input | Output | +| ---- | ----- | ------ | +| SetExtendedACL | [SetExtendedACLRequest](#container.SetExtendedACLRequest) | [SetExtendedACLResponse](#container.SetExtendedACLResponse) | +#### Method GetExtendedACL + +GetExtendedACL returns extended ACL rules of the container + +| Name | Input | Output | +| ---- | ----- | ------ | +| GetExtendedACL | [GetExtendedACLRequest](#container.GetExtendedACLRequest) | [GetExtendedACLResponse](#container.GetExtendedACLResponse) | @@ -104,6 +126,53 @@ via consensus in inner ring nodes + + +### Message ExtendedACLKey + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ID | [bytes](#bytes) | | ID (container id) is a SHA256 hash of the container structure | + + + + +### Message ExtendedACLValue + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| EACL | [bytes](#bytes) | | EACL carries binary representation of the table of extended ACL rules | +| Signature | [bytes](#bytes) | | Signature carries EACL field signature | + + + + +### Message GetExtendedACLRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Key | [ExtendedACLKey](#container.ExtendedACLKey) | | Key carries key to extended ACL information | +| Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) | +| Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) | + + + + +### Message GetExtendedACLResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ACL | [ExtendedACLValue](#container.ExtendedACLValue) | | ACL carries extended ACL information | + + ### Message GetRequest @@ -179,6 +248,27 @@ via consensus in inner ring nodes | ----- | ---- | ----- | ----------- | | CID | [bytes](#bytes) | | CID (container id) is a SHA256 hash of the container structure | + + + +### Message SetExtendedACLRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Key | [ExtendedACLKey](#container.ExtendedACLKey) | | Key carries key to extended ACL information | +| Value | [ExtendedACLValue](#container.ExtendedACLValue) | | Value carries extended ACL information | +| Meta | [service.RequestMetaHeader](#service.RequestMetaHeader) | | RequestMetaHeader contains information about request meta headers (should be embedded into message) | +| Verify | [service.RequestVerificationHeader](#service.RequestVerificationHeader) | | RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message) | + + + + +### Message SetExtendedACLResponse + + + diff --git a/docs/service.md b/docs/service.md index 0765f04..223ddd1 100644 --- a/docs/service.md +++ b/docs/service.md @@ -6,6 +6,8 @@ - [service/meta.proto](#service/meta.proto) - Messages + - [RequestExtendedHeader](#service.RequestExtendedHeader) + - [RequestExtendedHeader.KV](#service.RequestExtendedHeader.KV) - [RequestMetaHeader](#service.RequestMetaHeader) - [ResponseMetaHeader](#service.ResponseMetaHeader) @@ -13,6 +15,8 @@ - [service/verify.proto](#service/verify.proto) - Messages + - [BearerTokenMsg](#service.BearerTokenMsg) + - [BearerTokenMsg.Info](#service.BearerTokenMsg.Info) - [RequestVerificationHeader](#service.RequestVerificationHeader) - [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) - [Token](#service.Token) @@ -39,6 +43,29 @@ + + +### Message RequestExtendedHeader +RequestExtendedHeader contains extended headers of request + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Headers | [RequestExtendedHeader.KV](#service.RequestExtendedHeader.KV) | repeated | Headers carries list of key-value headers | + + + + +### Message RequestExtendedHeader.KV +KV contains string key-value pair + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| K | [string](#string) | | K carries extended header key | +| V | [string](#string) | | V carries extended header value | + + ### Message RequestMetaHeader @@ -52,6 +79,7 @@ RequestMetaHeader contains information about request meta headers | Epoch | [uint64](#uint64) | | Epoch for user can be empty, because node sets epoch to the actual value | | Version | [uint32](#uint32) | | Version defines protocol version TODO: not used for now, should be implemented in future | | Raw | [bool](#bool) | | Raw determines whether the request is raw or not | +| ExtendedHeader | [RequestExtendedHeader](#service.RequestExtendedHeader) | | ExtendedHeader carries extended headers of the request | @@ -81,6 +109,32 @@ ResponseMetaHeader contains meta information based on request processing by serv + + +### Message BearerTokenMsg +BearerTokenMsg carries information about request ACL rules with limited lifetime + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| TokenInfo | [BearerTokenMsg.Info](#service.BearerTokenMsg.Info) | | TokenInfo is a grouped information about token | +| OwnerKey | [bytes](#bytes) | | OwnerKey is a public key of the token owner | +| Signature | [bytes](#bytes) | | Signature is a signature of token information | + + + + +### Message BearerTokenMsg.Info + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| ACLRules | [bytes](#bytes) | | ACLRules carries a binary representation of the table of extended ACL rules | +| OwnerID | [bytes](#bytes) | | OwnerID is an owner of token | +| ValidUntil | [uint64](#uint64) | | ValidUntil carries a last epoch of token lifetime | + + ### Message RequestVerificationHeader @@ -92,6 +146,7 @@ RequestVerificationHeader is a set of signatures of every NeoFS Node that proces | ----- | ---- | ----- | ----------- | | Signatures | [RequestVerificationHeader.Signature](#service.RequestVerificationHeader.Signature) | repeated | Signatures is a set of signatures of every passed NeoFS Node | | Token | [Token](#service.Token) | | Token is a token of the session within which the request is sent | +| Bearer | [BearerTokenMsg](#service.BearerTokenMsg) | | Bearer is a Bearer token of the request | diff --git a/service/bearer.go b/service/bearer.go index bc8aaa5..6013e03 100644 --- a/service/bearer.go +++ b/service/bearer.go @@ -94,3 +94,33 @@ func copyBearerTokenSignedData(buf []byte, token BearerTokenInfo) { tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch()) off += 8 } + +// SetACLRules is an ACLRules field setter. +func (m *BearerTokenMsg_Info) SetACLRules(v []byte) { + m.ACLRules = v +} + +// SetValidUntil is a ValidUntil field setter. +func (m *BearerTokenMsg_Info) SetValidUntil(v uint64) { + m.ValidUntil = v +} + +// GetOwnerID if an OwnerID field getter. +func (m BearerTokenMsg_Info) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *BearerTokenMsg_Info) SetOwnerID(v OwnerID) { + m.OwnerID = v +} + +// SetOwnerKey is an OwnerKey field setter. +func (m *BearerTokenMsg) SetOwnerKey(v []byte) { + m.OwnerKey = v +} + +// SetSignature is a Signature field setter. +func (m *BearerTokenMsg) SetSignature(v []byte) { + m.Signature = v +} diff --git a/service/bearer_test.go b/service/bearer_test.go index da359f2..9ece9c8 100644 --- a/service/bearer_test.go +++ b/service/bearer_test.go @@ -170,3 +170,27 @@ func TestSignVerifyBearerToken(t *testing.T) { require.NoError(t, VerifySignatureWithKey(pk, verifiedToken)) } } + +func TestBearerTokenMsg_Setters(t *testing.T) { + s := new(BearerTokenMsg) + + aclRules := []byte{1, 2, 3} + s.SetACLRules(aclRules) + require.Equal(t, aclRules, s.GetACLRules()) + + validUntil := uint64(6) + s.SetValidUntil(validUntil) + require.Equal(t, validUntil, s.GetValidUntil()) + + ownerID := OwnerID{1, 2, 3} + s.SetOwnerID(ownerID) + require.Equal(t, ownerID, s.GetOwnerID()) + + ownerKey := []byte{4, 5, 6} + s.SetOwnerKey(ownerKey) + require.Equal(t, ownerKey, s.GetOwnerKey()) + + sig := []byte{7, 8, 9} + s.SetSignature(sig) + require.Equal(t, sig, s.GetSignature()) +} diff --git a/service/meta.go b/service/meta.go index f1b4613..e838cec 100644 --- a/service/meta.go +++ b/service/meta.go @@ -69,3 +69,18 @@ func (s extHdrSrcWrapper) ReadSignedData(p []byte) (int, error) { return sz, nil } + +// SetK is a K field setter. +func (m *RequestExtendedHeader_KV) SetK(v string) { + m.K = v +} + +// SetV is a V field setter. +func (m *RequestExtendedHeader_KV) SetV(v string) { + m.V = v +} + +// SetHeaders is a Headers field setter. +func (m *RequestExtendedHeader) SetHeaders(v []RequestExtendedHeader_KV) { + m.Headers = v +} diff --git a/service/meta.pb.go b/service/meta.pb.go index 25a54690ce276ae62f01f677ad59b35daf4d81d1..0d630774ff46662c326df5e1cdae3055baef51a9 100644 GIT binary patch literal 27975 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 delta 1650 zcmZXUy^0i35QSNF7G_)wHd7JDP+8&j?c0C&1I9jqiO}8CErKwK!JufH83_yh00u^8 zLdV7;zJXvU_y&R?Sob@#I=B-Qcj{KvId!UTfBL%j^ZAEvc5(UtuUW63MBn&$c5l3X z>3Dp*IGBF#|2RLsI-HHquD%<;ERWCnJQ*Kedo*pVu~+*UeMyAlE( zMS2)@L~bqB>H+|{?2nb(UPpSvrc7~>U2zWNtvI(X-llY(a(0wx@s!Bcl^|q0$wH8- zPO?b=NhMD_4<|w+dY?qJa7xcUfDn*U*FiN9NwX4~YzI}-*2+0bJ0b;80h7m=VUwYx z{fUFt;FRs4;i!?4Q6f?xp@B;#t4PM(6E7iC6!99EL`wLRkd|7x!EIDSQhInN^ya}7 zZ!dWmcSDSr)*1lTMiN`-N!G-aobRM%j6wKu0}VfiQlLh*qg#Vwj@y#nOpAz6)0U%Z zU>g$BO1i2^>qu)Us?AjA+FP;6jH%&QBv(9*Ov_r)gI}gn#lI`N}S|dcO z(Su_eMwMFFH#@|(pEo9^k6Bz|E5Y>i4zXb zj2!#`raNVOkYyYC*B8KKiwGH05+$bxby8ySVzKC_D>rVRnJ(Y_cy9Vu{@NRxI}gV7 ag~RFft%JSk``!2R>FND9^XYT{a{mvA-kr1n diff --git a/service/meta.proto b/service/meta.proto index 093f118..8171980 100644 --- a/service/meta.proto +++ b/service/meta.proto @@ -19,6 +19,8 @@ message RequestMetaHeader { uint32 Version = 3; // Raw determines whether the request is raw or not bool Raw = 4; + // ExtendedHeader carries extended headers of the request + RequestExtendedHeader ExtendedHeader = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; } // ResponseMetaHeader contains meta information based on request processing by server @@ -30,3 +32,18 @@ message ResponseMetaHeader { // TODO: not used for now, should be implemented in future uint32 Version = 2; } + +// RequestExtendedHeader contains extended headers of request +message RequestExtendedHeader { + // KV contains string key-value pair + message KV { + // K carries extended header key + string K = 1; + + // V carries extended header value + string V = 2; + } + + // Headers carries list of key-value headers + repeated KV Headers = 1 [(gogoproto.nullable) = false]; +} diff --git a/service/meta_test.go b/service/meta_test.go index a0b85ef..7c3853a 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -23,3 +23,31 @@ func TestCutRestoreMeta(t *testing.T) { require.Equal(t, item(), v1) } } + +func TestRequestExtendedHeader_KV_Setters(t *testing.T) { + s := new(RequestExtendedHeader_KV) + + key := "key" + s.SetK(key) + require.Equal(t, key, s.GetK()) + + val := "val" + s.SetV(val) + require.Equal(t, val, s.GetV()) +} + +func TestRequestExtendedHeader_SetHeaders(t *testing.T) { + s := new(RequestExtendedHeader) + + hdr := RequestExtendedHeader_KV{} + hdr.SetK("key") + hdr.SetV("val") + + hdrs := []RequestExtendedHeader_KV{ + hdr, + } + + s.SetHeaders(hdrs) + + require.Equal(t, hdrs, s.GetHeaders()) +} diff --git a/service/verify.go b/service/verify.go index 62db2f5..0673a01 100644 --- a/service/verify.go +++ b/service/verify.go @@ -67,6 +67,11 @@ func (m *RequestVerificationHeader) SetToken(token *Token) { m.Token = token } +// SetBearer is a Bearer field setter. +func (m *RequestVerificationHeader) SetBearer(v *BearerTokenMsg) { + m.Bearer = v +} + // testCustomField for test usage only. type testCustomField [8]uint32 diff --git a/service/verify.pb.go b/service/verify.pb.go index d198302b4ca428e96b4d5b34089043bf8d99a369..02f3f39c2ef444cc1361ae9ac0ba34ff0b1d7ba6 100644 GIT binary patch delta 6174 zcmaJ_U2Ggz6_%YQuxm(SLL96(&Dd^S+gaN)`@eRRCTXgWl9Z4pQIimwnb|w(QhV*} zuIZ+cH39KLO9agod80HhNPR#c7EuW)0#!vqfS{G&g%?_hHw5DE36R3M-#5EnkC)_O zGQL0OeCM3+{OtVhr~QBb(Uu=Po!B;gZuuou)t^rs59j8rW6G_mnpLS=Zu`Aq_O0c> zYP_hdn!3KK>WyR~I%sY9H_cT@JYBzLU0tayHb%;k( zIFT8%zF1#QROiRIst2vqHcHm_69*qLulm8n%oh2-wLLkpRSyh}?VGYrs0J@gPFXSC z`kUMLZR2KZbjQV!;f`?~biP}wRa9MnIJ9davdcR9)G4dBx~S^b%az89)?#I;(okjj zKTwUzl1esitf(BbXW)Ly!?=Zb!9e_m(ze@a&q1NtBPXY{2#`}LRi9P7*I zclW##JFm|?J{-e;bIn>y2CG%ynq1ms+v|@%8Iy2l_|e#k{=(>)k<421|4X?ya6EqJ zx5ID8mmcg&=Qmn6ds@}5smZ03Rj>I|4^i;?C+FhQu8&X0m)z-1OKZCi#83Uj)m63OHY&>< zk~ds$Q5nXoWv>z~6U$KmM!jf9 zL?0QN_-N;k(H*-CyLw4$O&@%sU;imJG_%pxsmW?(aVlkv)m80F6<^J6+}0nahU3;g zNDaj_%o?(4f96|T^0}h^&&;*3loi)Eb9nk?^PX4cSvDbQ=G;^=VK*Jy{NZ9FkBxM} ztQ7KC$1@X{_cGQ4Jj*_IJ)Fcd3k6h`&YATvmxBf$0Nk`$D`BTtH1BELaIxZGH;ZSH z@7N_&8|NL@+{!uT=>m0zCZtQ)zy%K*1!G9qEKwe|^FUd`?J^`OygRUjHoMTHV6Bf` z!D5`0htUeMvk>P|1TGW2#|sdVGbh7l0e{%=p+kS-LKcD?_&no5L$S+-G|dHg53uuq z2ov`!JQ^ZP^L4xce z-l;Q--ExU0Km^4V#KKTQj2qOW@Lpg@fH=cI({gHnh|(sEqQ{s}AV?TOPI?ezwAoF* zm+@|k+eE)Zia}3-1R%u`tH>(QQ_{d_$U=j{QUFLUOr(3rdh#ZKIeB0qV`+|XQm_=M zpS0r9LKzw|%pySY0fw|9$IAe016~voXs}@?Z6huZCw*93WFYfUU_-Q|RcP`AHYnUC zyGbYoXRs*HG0ba;!om{p(UKyZPTZ88hlnDwgf=j51dGw&(Qh!FoXi1I30_SDl8wtz24bZgVw+xN zsf*}KmcX~-d1!ze*c;f#TwIo#C5$OTe*_F1VyUB@3=*-hGNd6o8$yrNY@;E745AF6 zt%y@>vuubAXp}`bpQUEn1N%MPWljV%f$fcb$79L@d{{yvusauUn}wArLzEd7P(Woh zNHZs(nL!c?853k+5@*npX}m`l0FV8sfS7a22w7fPO!L5!13cPH57Q$#_6C|x5-~yP zBo>_Rw#?4XmP#i%5Y14@;3Y^yHV1lDL>L>SEXxjIuq{Vr4E$u}APv}k7^Fc|bII}|gMgd%vlhg~^x5>u982)dZT(UZ)8 z&%>q<#B3V$62ri9$+s&OEf?XjblMR7Fm3gr>=8@?kRzud-jW z>`VPJ8kjj{z~czP%wa%_kVmIS0|A_8W1S&kKje%?Zb!Y5Lx8D}GXIp|A0`F)&IvDS z$fzP5#sj#4GDvIoKn~qLDUMtbb6^6N$P!j%h0wE>nOZ9PSy-7G0h~d^e5cKv7Z^zf zEXRzNu~N9|;u@Q3MGh9$xs5#nTssK*o9;y4m14$8av8 zKbZ%tpZpO;@GL>{v&QIW*(X^eozURIw=y0Z8|X6pW@>U)COf1}W-bKs1R z%;1caMpj9{rbWL@??wYd$zMGzrsOzRY)5}%VPsq&8;?OCQ-(c1Y650%Z2e}T(jR>a z)K46?+OO{6dws5c;kmu7Pak%7eWJ%_wg(HpivNVaezX=_YPnCB`}!SMzkBjtpQ8sq zGaUPk&yOYU;W0gM;yba+`uiu|>v?>$o;xRht?!?4^f-gG~4-r5k9kf z{cFbGj_vMzrkc|4otf58KC^9o`~9bmwBGvMVE+Mrv9LxDS@J2vd@D;VT8FHqS+37M zS9wKEPU-j0-O|q+eW%+9twZa`6N&XzYhvTKn44>2 zS^^L8oT}BV`E@gO-|FSo&gut!`u5pjZ4G{`^|pE~)5Fl#&n|t?7mwc?*DlAGX0Cj< zr>%tluT3KU+fe$A@KWpJx9|3Mo89{H>a9m&!dnZk{I$R1vDVJpAL$=yb3j7puB^!S k%5GlFe|>WO^JmOm?TdQ0IVp-Swf-6YzP~kg_lJr90oH8x7XSbN delta 3591 zcmY+HxvFJF6o%>B4%`c(Vj!a6F))#f?0FD6a!906)EC&Jh+?bJj(Ue;VA3ZxGB9o2 z9oyh5XskYfh`xbU-&aC=gJPc=*1v{-{Z;p$2RDCxcya&j%jZx3|9kDDJD)C}pHAm@ zFW)-9{>nG!ySHCFe!KenvE$8W-g)Wx?u|choL|0m?`GZh=Lfew-rMT>lx=kx$7+pZ z!f_#q zy^Rh`ac0#?W(vOtb}}+;F0N>*uTcreal#$M`CCRx+ldgsc%VfP7y0+2EL89V|An$1!%RY zCt6EYfmT-lxLo5*DE(;8S43`XomrA^ zIo=m6>vx75&dctCJ(&e%XDSj1E2CYdg?S}}nV6U5dJj}8`H@*Z&&hrJhd3><%ruks zr}1mMCAZlGLWeWz%1Y=tW9mTJ*n(e}B~AyX2ahDEv=+Jduts|jtj~b+Ib(&xErARe zvKY#jdvZdv4~GhT32OC}hRGNVak3|{r+~@N&@#c*2A4WQs6jTjBJ`Y9R~4lsD>A9h z_GlwWR2T=mS4KS%*bwG>5E}3U1H(+HTNo9t)D2KCm7ed=Xi=2RUhM(Q3Dz)^H{h2b zDCXK*nMtxs>Lsx;7gTSy46Bw%#_$O1k z>6Be|T7kWWO-5qBDNPeeQn(bzoc$&&V^t}g=N$xf+`h7)BQEvqHLatxVw3<4-{wKQwK4Y4m=V8f6B_(EvKO!V&bQ0*2ZzaPN zD!y{77htCjQd5u3mBk369SSoIASCY5X@=;Gnz`awPW8MMjM;9`^uTI{QGLn85Aq&k zFo`BN6%n*#PhR7Z3x3Rc@Kg184KqWXxfP z5U5xe=^oxc^ro#b;h*-rYZ~~ebnB`G)eBSQe)}-J#k>}!ug$Fl^5#p=Q#AX(_n&n1 za`Mtj?^#N(4x}fs9gX*!2W+2;DtQ%a&O5MyXx;}ST_Ss6^}gBTN}!|yK&;5KsZ(Y?MKl|#KSHq*1> zMM+fptF_ukw;b}nfk9CMAU&so^8w#0*Oy$X{}|&J@;gMJF4G~vfAY3SPOC# zbv0bkPX>f2pvQXqlZ)er_rH4X`2MqJFV0_l`N8?84_`g*-ue0Qqu3r^9)I2cu<~B_h0Tk@h_#Ek}3cI diff --git a/service/verify.proto b/service/verify.proto index a6619a6..a7e694f 100644 --- a/service/verify.proto +++ b/service/verify.proto @@ -23,6 +23,9 @@ message RequestVerificationHeader { // Token is a token of the session within which the request is sent Token Token = 2; + + // Bearer is a Bearer token of the request + BearerTokenMsg Bearer = 3; } // User token granting rights for object manipulation @@ -91,3 +94,26 @@ message TokenLifetime { // uint32 Version = 2; // bytes Data = 3; // } + +// BearerTokenMsg carries information about request ACL rules with limited lifetime +message BearerTokenMsg { + message Info { + // ACLRules carries a binary representation of the table of extended ACL rules + bytes ACLRules = 1; + + // OwnerID is an owner of token + bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false]; + + // ValidUntil carries a last epoch of token lifetime + uint64 ValidUntil = 3; + } + + // TokenInfo is a grouped information about token + Info TokenInfo = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + + // OwnerKey is a public key of the token owner + bytes OwnerKey = 2; + + // Signature is a signature of token information + bytes Signature = 3; +} diff --git a/service/verify_test.go b/service/verify_test.go index e13f316..55ec65f 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -115,3 +115,16 @@ func TestRequestVerificationHeader_SetToken(t *testing.T) { require.Equal(t, token, h.GetToken()) } + +func TestRequestVerificationHeader_SetBearer(t *testing.T) { + aclRules := []byte{1, 2, 3} + + token := new(BearerTokenMsg) + token.SetACLRules(aclRules) + + h := new(RequestVerificationHeader) + + h.SetBearer(token) + + require.Equal(t, token, h.GetBearer()) +} From 3f7d3f8a86d5df90e222e64f16b735b81766407c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 15:24:17 +0300 Subject: [PATCH 05/11] service: make RequestData to provide BearerTokenSource interface --- service/bearer.go | 30 ++++++++++++++++++++++++++++++ service/bearer_test.go | 15 +++++++++++++++ service/sign_test.go | 6 ++++++ service/types.go | 1 + service/verify.go | 11 +++++++++++ service/verify_test.go | 10 ++++++++++ 6 files changed, 73 insertions(+) diff --git a/service/bearer.go b/service/bearer.go index 6013e03..dc556ce 100644 --- a/service/bearer.go +++ b/service/bearer.go @@ -12,6 +12,10 @@ type signedBearerToken struct { BearerToken } +type bearerMsgWrapper struct { + *BearerTokenMsg +} + const fixedBearerTokenDataSize = 0 + refs.OwnerIDSize + 8 @@ -124,3 +128,29 @@ func (m *BearerTokenMsg) SetOwnerKey(v []byte) { func (m *BearerTokenMsg) SetSignature(v []byte) { m.Signature = v } + +func wrapBearerTokenMsg(msg *BearerTokenMsg) bearerMsgWrapper { + return bearerMsgWrapper{ + BearerTokenMsg: msg, + } +} + +// ExpirationEpoch returns the result of ValidUntil field getter. +// +// If message is nil, 0 returns. +func (s bearerMsgWrapper) ExpirationEpoch() uint64 { + if s.BearerTokenMsg != nil { + return s.GetValidUntil() + } + + return 0 +} + +// SetExpirationEpoch passes argument to ValidUntil field setter. +// +// If message is nil, nothing changes. +func (s bearerMsgWrapper) SetExpirationEpoch(v uint64) { + if s.BearerTokenMsg != nil { + s.SetValidUntil(v) + } +} diff --git a/service/bearer_test.go b/service/bearer_test.go index 9ece9c8..381f190 100644 --- a/service/bearer_test.go +++ b/service/bearer_test.go @@ -194,3 +194,18 @@ func TestBearerTokenMsg_Setters(t *testing.T) { s.SetSignature(sig) require.Equal(t, sig, s.GetSignature()) } + +func TestBearerMsgWrapper_ExpirationEpoch(t *testing.T) { + s := wrapBearerTokenMsg(nil) + require.Zero(t, s.ExpirationEpoch()) + require.NotPanics(t, func() { + s.SetExpirationEpoch(1) + }) + + msg := new(BearerTokenMsg) + s = wrapBearerTokenMsg(msg) + + epoch := uint64(7) + s.SetExpirationEpoch(epoch) + require.Equal(t, epoch, s.ExpirationEpoch()) +} diff --git a/service/sign_test.go b/service/sign_test.go index ca469b8..8b67e5b 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -18,6 +18,8 @@ type testSignedDataSrc struct { sig []byte key *ecdsa.PublicKey token SessionToken + + bearer BearerToken } type testSignedDataReader struct { @@ -54,6 +56,10 @@ func (s testSignedDataSrc) GetSessionToken() SessionToken { return s.token } +func (s testSignedDataSrc) GetBearerToken() BearerToken { + return s.bearer +} + func (s testSignedDataReader) SignedDataSize() int { return len(s.data) } diff --git a/service/types.go b/service/types.go index 87c3a77..feba2e3 100644 --- a/service/types.go +++ b/service/types.go @@ -254,6 +254,7 @@ type DataWithSignKeySource interface { type RequestData interface { SignedDataSource SessionTokenSource + BearerTokenSource } // RequestSignedData is an interface of request information with signature write access. diff --git a/service/verify.go b/service/verify.go index 0673a01..9fbdfdf 100644 --- a/service/verify.go +++ b/service/verify.go @@ -103,3 +103,14 @@ func (t testCustomField) MarshalTo(data []byte) (int, error) { return 0, nil } // Marshal skip, it's for test usage only. func (t testCustomField) Marshal() ([]byte, error) { return nil, nil } + +// GetBearerToken returns wraps Bearer field and return BearerToken interface. +// +// If Bearer field value is nil, nil returns. +func (m RequestVerificationHeader) GetBearerToken() BearerToken { + if t := m.GetBearer(); t != nil { + return wrapBearerTokenMsg(t) + } + + return nil +} diff --git a/service/verify_test.go b/service/verify_test.go index 55ec65f..b42bb79 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -128,3 +128,13 @@ func TestRequestVerificationHeader_SetBearer(t *testing.T) { require.Equal(t, token, h.GetBearer()) } + +func TestRequestVerificationHeader_GetBearerToken(t *testing.T) { + s := new(RequestVerificationHeader) + + require.Nil(t, s.GetBearerToken()) + + bearer := new(BearerTokenMsg) + s.SetBearer(bearer) + require.Equal(t, wrapBearerTokenMsg(bearer), s.GetBearerToken()) +} From a3569ad99e7f016cc59e20d9f41ef08bdd98c72c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 15:26:56 +0300 Subject: [PATCH 06/11] service: ad BearerToken to signed payload of the requests --- service/sign.go | 6 ++++++ service/sign_test.go | 19 +++++++++++++++++++ service/verify.go | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/service/sign.go b/service/sign.go index eb1c16d..a0bb7e5 100644 --- a/service/sign.go +++ b/service/sign.go @@ -209,6 +209,9 @@ func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error { NewSignedSessionToken( src.GetSessionToken(), ), + NewSignedBearerToken( + src.GetBearerToken(), + ), ) if err != nil { return err @@ -231,6 +234,9 @@ func VerifyRequestData(src RequestVerifyData) error { NewVerifiedSessionToken( src.GetSessionToken(), ), + NewVerifiedBearerToken( + src.GetBearerToken(), + ), ) if err != nil { return err diff --git a/service/sign_test.go b/service/sign_test.go index 8b67e5b..80c0d19 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -279,14 +279,21 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { var ( token = new(Token) initVerb = Token_Info_Verb(1) + + bearer = wrapBearerTokenMsg(new(BearerTokenMsg)) + bearerEpoch = uint64(8) ) token.SetVerb(initVerb) + bearer.SetExpirationEpoch(bearerEpoch) + // create test data with token src := &testSignedDataSrc{ data: testData(t, 10), token: token, + + bearer: bearer, } // create test private key @@ -319,6 +326,18 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { // ascertain that verification is passed require.NoError(t, VerifyRequestData(src)) + // break the Bearer token + bearer.SetExpirationEpoch(bearerEpoch + 1) + + // ascertain that verification is failed + require.Error(t, VerifyRequestData(src)) + + // restore the Bearer token + bearer.SetExpirationEpoch(bearerEpoch) + + // ascertain that verification is passed + require.NoError(t, VerifyRequestData(src)) + // wrap to data reader rdr := &testSignedDataReader{ testSignedDataSrc: src, diff --git a/service/verify.go b/service/verify.go index 9fbdfdf..ee2eecc 100644 --- a/service/verify.go +++ b/service/verify.go @@ -104,7 +104,7 @@ func (t testCustomField) MarshalTo(data []byte) (int, error) { return 0, nil } // Marshal skip, it's for test usage only. func (t testCustomField) Marshal() ([]byte, error) { return nil, nil } -// GetBearerToken returns wraps Bearer field and return BearerToken interface. +// GetBearerToken wraps Bearer field and return BearerToken interface. // // If Bearer field value is nil, nil returns. func (m RequestVerificationHeader) GetBearerToken() BearerToken { From db53e2ea39ab6ab7c2a6d0a5980a018a40e0aa6b Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 15:47:55 +0300 Subject: [PATCH 07/11] service: make RequestData to provide ExtendedHeadersSource interface --- service/meta.go | 45 +++++++++++++++++++++++++++++++++++++++++++ service/meta_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++ service/sign_test.go | 6 ++++++ service/types.go | 1 + 4 files changed, 98 insertions(+) diff --git a/service/meta.go b/service/meta.go index e838cec..557d076 100644 --- a/service/meta.go +++ b/service/meta.go @@ -4,6 +4,10 @@ import ( "io" ) +type extHdrWrapper struct { + msg *RequestExtendedHeader_KV +} + type extHdrSrcWrapper struct { extHdrSrc ExtendedHeadersSource } @@ -84,3 +88,44 @@ func (m *RequestExtendedHeader_KV) SetV(v string) { func (m *RequestExtendedHeader) SetHeaders(v []RequestExtendedHeader_KV) { m.Headers = v } + +func wrapExtendedHeaderKV(msg *RequestExtendedHeader_KV) extHdrWrapper { + return extHdrWrapper{ + msg: msg, + } +} + +// Key returns the result of K field getter. +// +// If message is nil, empty string returns. +func (m extHdrWrapper) Key() string { + if m.msg != nil { + return m.msg.GetK() + } + + return "" +} + +// Value returns the result of V field getter. +// +// If message is nil, empty string returns. +func (m extHdrWrapper) Value() string { + if m.msg != nil { + return m.msg.GetV() + } + + return "" +} + +// ExtendedHeaders composes ExtendedHeader list from the Headers field getter result. +func (m RequestExtendedHeader) ExtendedHeaders() []ExtendedHeader { + hs := m.GetHeaders() + + res := make([]ExtendedHeader, 0, len(hs)) + + for i := range hs { + res = append(res, wrapExtendedHeaderKV(&hs[i])) + } + + return res +} diff --git a/service/meta_test.go b/service/meta_test.go index 7c3853a..9e9c012 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -51,3 +51,49 @@ func TestRequestExtendedHeader_SetHeaders(t *testing.T) { require.Equal(t, hdrs, s.GetHeaders()) } + +func TestExtHdrWrapper(t *testing.T) { + s := wrapExtendedHeaderKV(nil) + require.Empty(t, s.Key()) + require.Empty(t, s.Value()) + + msg := new(RequestExtendedHeader_KV) + s = wrapExtendedHeaderKV(msg) + + key := "key" + msg.SetK(key) + require.Equal(t, key, s.Key()) + + val := "val" + msg.SetV(val) + require.Equal(t, val, s.Value()) +} + +func TestRequestExtendedHeader_ExtendedHeaders(t *testing.T) { + var ( + k1, v1 = "key1", "value1" + k2, v2 = "key2", "value2" + h1 = new(RequestExtendedHeader_KV) + h2 = new(RequestExtendedHeader_KV) + ) + + h1.SetK(k1) + h1.SetV(v1) + + h2.SetK(k2) + h2.SetV(v2) + + s := new(RequestExtendedHeader) + s.SetHeaders([]RequestExtendedHeader_KV{ + *h1, *h2, + }) + + xHdrs := s.ExtendedHeaders() + require.Len(t, xHdrs, 2) + + require.Equal(t, k1, xHdrs[0].Key()) + require.Equal(t, v1, xHdrs[0].Value()) + + require.Equal(t, k2, xHdrs[1].Key()) + require.Equal(t, v2, xHdrs[1].Value()) +} diff --git a/service/sign_test.go b/service/sign_test.go index 80c0d19..023412f 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -20,6 +20,8 @@ type testSignedDataSrc struct { token SessionToken bearer BearerToken + + extHdrs []ExtendedHeader } type testSignedDataReader struct { @@ -60,6 +62,10 @@ func (s testSignedDataSrc) GetBearerToken() BearerToken { return s.bearer } +func (s testSignedDataSrc) ExtendedHeaders() []ExtendedHeader { + return s.extHdrs +} + func (s testSignedDataReader) SignedDataSize() int { return len(s.data) } diff --git a/service/types.go b/service/types.go index feba2e3..75a5a0a 100644 --- a/service/types.go +++ b/service/types.go @@ -255,6 +255,7 @@ type RequestData interface { SignedDataSource SessionTokenSource BearerTokenSource + ExtendedHeadersSource } // RequestSignedData is an interface of request information with signature write access. From c360b7d19cc253a233dfe2df8c434a6063c3ce29 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 15:50:01 +0300 Subject: [PATCH 08/11] service: add ExtendedHeader list to signed payload of the requests --- service/sign.go | 2 ++ service/sign_test.go | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/service/sign.go b/service/sign.go index a0bb7e5..50453b9 100644 --- a/service/sign.go +++ b/service/sign.go @@ -212,6 +212,7 @@ func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error { NewSignedBearerToken( src.GetBearerToken(), ), + ExtendedHeadersSignedData(src), ) if err != nil { return err @@ -237,6 +238,7 @@ func VerifyRequestData(src RequestVerifyData) error { NewVerifiedBearerToken( src.GetBearerToken(), ), + ExtendedHeadersSignedData(src), ) if err != nil { return err diff --git a/service/sign_test.go b/service/sign_test.go index 023412f..724c068 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -268,7 +268,7 @@ func TestVerifySignatureWithKey(t *testing.T) { require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src)) } -func TestSignVerifyDataWithSessionToken(t *testing.T) { +func TestSignVerifyRequestData(t *testing.T) { // sign with empty RequestSignedData require.EqualError(t, SignRequestData(nil, nil), @@ -288,18 +288,27 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { bearer = wrapBearerTokenMsg(new(BearerTokenMsg)) bearerEpoch = uint64(8) + + extHdrKey = "key" + extHdr = new(RequestExtendedHeader_KV) ) token.SetVerb(initVerb) bearer.SetExpirationEpoch(bearerEpoch) + extHdr.SetK(extHdrKey) + // create test data with token src := &testSignedDataSrc{ data: testData(t, 10), token: token, bearer: bearer, + + extHdrs: []ExtendedHeader{ + wrapExtendedHeaderKV(extHdr), + }, } // create test private key @@ -344,6 +353,18 @@ func TestSignVerifyDataWithSessionToken(t *testing.T) { // ascertain that verification is passed require.NoError(t, VerifyRequestData(src)) + // break the extended header + extHdr.SetK(extHdrKey + "1") + + // ascertain that verification is failed + require.Error(t, VerifyRequestData(src)) + + // restore the extended header + extHdr.SetK(extHdrKey) + + // ascertain that verification is passed + require.NoError(t, VerifyRequestData(src)) + // wrap to data reader rdr := &testSignedDataReader{ testSignedDataSrc: src, From a3c488994521ae9282de58c762de46bbc7ab4a21 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 16:06:47 +0300 Subject: [PATCH 09/11] service: implement BearerToken interface on BearerTokenMsg message This commit: * implements (Set)ExpirationEpoch methods on BearerTokenMsg; * removes BearerTokenMsg wrapper. --- service/bearer.go | 36 ++++++++++-------------------------- service/bearer_test.go | 18 +++--------------- service/sign_test.go | 2 +- service/verify.go | 2 +- service/verify_test.go | 2 +- 5 files changed, 16 insertions(+), 44 deletions(-) diff --git a/service/bearer.go b/service/bearer.go index dc556ce..327f74f 100644 --- a/service/bearer.go +++ b/service/bearer.go @@ -119,6 +119,16 @@ func (m *BearerTokenMsg_Info) SetOwnerID(v OwnerID) { m.OwnerID = v } +// ExpirationEpoch returns the result of ValidUntil field getter. +func (m BearerTokenMsg_Info) ExpirationEpoch() uint64 { + return m.GetValidUntil() +} + +// SetExpirationEpoch passes argument to ValidUntil field setter. +func (m *BearerTokenMsg_Info) SetExpirationEpoch(v uint64) { + m.SetValidUntil(v) +} + // SetOwnerKey is an OwnerKey field setter. func (m *BearerTokenMsg) SetOwnerKey(v []byte) { m.OwnerKey = v @@ -128,29 +138,3 @@ func (m *BearerTokenMsg) SetOwnerKey(v []byte) { func (m *BearerTokenMsg) SetSignature(v []byte) { m.Signature = v } - -func wrapBearerTokenMsg(msg *BearerTokenMsg) bearerMsgWrapper { - return bearerMsgWrapper{ - BearerTokenMsg: msg, - } -} - -// ExpirationEpoch returns the result of ValidUntil field getter. -// -// If message is nil, 0 returns. -func (s bearerMsgWrapper) ExpirationEpoch() uint64 { - if s.BearerTokenMsg != nil { - return s.GetValidUntil() - } - - return 0 -} - -// SetExpirationEpoch passes argument to ValidUntil field setter. -// -// If message is nil, nothing changes. -func (s bearerMsgWrapper) SetExpirationEpoch(v uint64) { - if s.BearerTokenMsg != nil { - s.SetValidUntil(v) - } -} diff --git a/service/bearer_test.go b/service/bearer_test.go index 381f190..d6985cb 100644 --- a/service/bearer_test.go +++ b/service/bearer_test.go @@ -182,6 +182,9 @@ func TestBearerTokenMsg_Setters(t *testing.T) { s.SetValidUntil(validUntil) require.Equal(t, validUntil, s.GetValidUntil()) + s.SetExpirationEpoch(validUntil + 1) + require.Equal(t, validUntil+1, s.ExpirationEpoch()) + ownerID := OwnerID{1, 2, 3} s.SetOwnerID(ownerID) require.Equal(t, ownerID, s.GetOwnerID()) @@ -194,18 +197,3 @@ func TestBearerTokenMsg_Setters(t *testing.T) { s.SetSignature(sig) require.Equal(t, sig, s.GetSignature()) } - -func TestBearerMsgWrapper_ExpirationEpoch(t *testing.T) { - s := wrapBearerTokenMsg(nil) - require.Zero(t, s.ExpirationEpoch()) - require.NotPanics(t, func() { - s.SetExpirationEpoch(1) - }) - - msg := new(BearerTokenMsg) - s = wrapBearerTokenMsg(msg) - - epoch := uint64(7) - s.SetExpirationEpoch(epoch) - require.Equal(t, epoch, s.ExpirationEpoch()) -} diff --git a/service/sign_test.go b/service/sign_test.go index 724c068..6f1e913 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -286,7 +286,7 @@ func TestSignVerifyRequestData(t *testing.T) { token = new(Token) initVerb = Token_Info_Verb(1) - bearer = wrapBearerTokenMsg(new(BearerTokenMsg)) + bearer = new(BearerTokenMsg) bearerEpoch = uint64(8) extHdrKey = "key" diff --git a/service/verify.go b/service/verify.go index ee2eecc..e1caa06 100644 --- a/service/verify.go +++ b/service/verify.go @@ -109,7 +109,7 @@ func (t testCustomField) Marshal() ([]byte, error) { return nil, nil } // If Bearer field value is nil, nil returns. func (m RequestVerificationHeader) GetBearerToken() BearerToken { if t := m.GetBearer(); t != nil { - return wrapBearerTokenMsg(t) + return t } return nil diff --git a/service/verify_test.go b/service/verify_test.go index b42bb79..5ab8753 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -136,5 +136,5 @@ func TestRequestVerificationHeader_GetBearerToken(t *testing.T) { bearer := new(BearerTokenMsg) s.SetBearer(bearer) - require.Equal(t, wrapBearerTokenMsg(bearer), s.GetBearerToken()) + require.Equal(t, bearer, s.GetBearerToken()) } From 03bc5c5f895f299d63a6a02e6b4117aef2f63639 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 16:26:44 +0300 Subject: [PATCH 10/11] container: implement SignedDataSource interface on EACL messages --- container/sign.go | 57 ++++++++++++++++++++++++++++++++++++++++++ container/sign_test.go | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/container/sign.go b/container/sign.go index eafd93c..f538f2d 100644 --- a/container/sign.go +++ b/container/sign.go @@ -135,3 +135,60 @@ func (m ListRequest) ReadSignedData(p []byte) (int, error) { return off, nil } + +// SignedData returns payload bytes of the request. +func (m GetExtendedACLRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m GetExtendedACLRequest) SignedDataSize() int { + return m.GetID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetExtendedACLRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetID().Bytes()) + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m SetExtendedACLRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m SetExtendedACLRequest) SignedDataSize() int { + return 0 + + m.GetID().Size() + + len(m.GetEACL()) + + len(m.GetSignature()) +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m SetExtendedACLRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetID().Bytes()) + + off += copy(p[off:], m.GetEACL()) + + off += copy(p[off:], m.GetSignature()) + + return off, nil +} diff --git a/container/sign_test.go b/container/sign_test.go index d9b7d26..d04a698 100644 --- a/container/sign_test.go +++ b/container/sign_test.go @@ -108,6 +108,50 @@ func TestRequestSign(t *testing.T) { }, }, }, + { // GetExtendedACLRequest + constructor: func() sigType { + return new(GetExtendedACLRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*GetExtendedACLRequest) + + id := req.GetID() + id[0]++ + + req.SetID(id) + }, + }, + }, + { // SetExtendedACLRequest + constructor: func() sigType { + return new(SetExtendedACLRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + id := req.GetID() + id[0]++ + + req.SetID(id) + }, + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + req.SetEACL( + append(req.GetEACL(), 1), + ) + }, + func(s sigType) { + req := s.(*SetExtendedACLRequest) + + req.SetSignature( + append(req.GetSignature(), 1), + ) + }, + }, + }, } for _, item := range items { From d3984cf236e822c361055e08c96783ffd6d4df4d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 18 Jun 2020 17:25:39 +0300 Subject: [PATCH 11/11] Update changelog and readme for v1.1.0 --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd6afb..e771c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog This is the changelog for NeoFS-API-Go +## [1.1.0] - 2020-06-18 + +### Added + +- `container.SetExtendedACL` rpc. +- `container.GetExtendedACL` rpc. +- Bearer token to all request messages. +- X-headers to all request messages. + +### Changed + +- Implementation and signatures of Sign/Verify request functions. + +### Updated + +- NeoFS API v1.0.0 => 1.1.0 + ## [1.0.0] - 2020-05-26 - Bump major release @@ -339,3 +356,4 @@ Initial public release [0.7.5]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.4...v0.7.5 [0.7.6]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.5...v0.7.6 [1.0.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.6...v1.0.0 +[1.1.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v1.0.0...v1.1.0 diff --git a/README.md b/README.md index fac04d0..d44e057 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ NeoFS API repository contains implementation of core NeoFS structures that can be used for integration with NeoFS. +## Сompatibility + +[neofs-api v1.1.0]: https://github.com/nspcc-dev/neofs-api/releases/tag/v1.1.0 +[neofs-api-go v1.1.0]: https://github.com/nspcc-dev/neofs-api-go/releases/tag/v1.1.0 +[neofs-api-go v1.1.0] supports [neofs-api v1.1.0] + ## Description Repository contains 13 packages that implement NeoFS core structures. These