diff --git a/accounting/sign.go b/accounting/sign.go new file mode 100644 index 00000000..1eabed4f --- /dev/null +++ b/accounting/sign.go @@ -0,0 +1,167 @@ +package accounting + +import ( + "encoding/binary" + "io" + + "github.com/nspcc-dev/neofs-api-go/service" +) + +// SignedData returns payload bytes of the request. +func (m BalanceRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m BalanceRequest) SignedDataSize() int { + return m.GetOwnerID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m BalanceRequest) ReadSignedData(p []byte) (int, error) { + sz := m.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + copy(p, m.GetOwnerID().Bytes()) + + return sz, nil +} + +// SignedData returns payload bytes of the request. +func (m GetRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m GetRequest) SignedDataSize() int { + return m.GetID().Size() + m.GetOwnerID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetRequest) ReadSignedData(p []byte) (int, error) { + sz := m.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetID().Bytes()) + + copy(p[off:], m.GetOwnerID().Bytes()) + + return sz, nil +} + +// SignedData returns payload bytes of the request. +func (m PutRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m PutRequest) SignedDataSize() (sz int) { + sz += m.GetOwnerID().Size() + + sz += m.GetMessageID().Size() + + sz += 8 + + if amount := m.GetAmount(); amount != nil { + sz += amount.Size() + } + + return +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m PutRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetOwnerID().Bytes()) + + off += copy(p[off:], m.GetMessageID().Bytes()) + + binary.BigEndian.PutUint64(p[off:], m.GetHeight()) + off += 8 + + if amount := m.GetAmount(); amount != nil { + n, err := amount.MarshalTo(p[off:]) + off += n + if err != nil { + return off + n, err + } + } + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m ListRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m ListRequest) SignedDataSize() int { + return m.GetOwnerID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m ListRequest) ReadSignedData(p []byte) (int, error) { + sz := m.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + copy(p, m.GetOwnerID().Bytes()) + + return sz, nil +} + +// SignedData returns payload bytes of the request. +func (m DeleteRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m DeleteRequest) SignedDataSize() (sz int) { + sz += m.GetID().Size() + + sz += m.GetOwnerID().Size() + + sz += m.GetMessageID().Size() + + return +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m DeleteRequest) 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.GetOwnerID().Bytes()) + + off += copy(p[off:], m.GetMessageID().Bytes()) + + return off, nil +} diff --git a/accounting/sign_test.go b/accounting/sign_test.go new file mode 100644 index 00000000..dd7a819f --- /dev/null +++ b/accounting/sign_test.go @@ -0,0 +1,185 @@ +package accounting + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/decimal" + "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestSignBalanceRequest(t *testing.T) { + sk := test.DecodeKey(0) + + type sigType interface { + service.SignedDataWithToken + service.SignKeyPairAccumulator + service.SignKeyPairSource + SetToken(*service.Token) + } + + items := []struct { + constructor func() sigType + payloadCorrupt []func(sigType) + }{ + { // BalanceRequest + constructor: func() sigType { + return new(BalanceRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*BalanceRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + }, + }, + { // GetRequest + constructor: func() sigType { + return new(GetRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*GetRequest) + + id, err := NewChequeID() + require.NoError(t, err) + + req.SetID(id) + }, + func(s sigType) { + req := s.(*GetRequest) + + id := req.GetOwnerID() + id[0]++ + + req.SetOwnerID(id) + }, + }, + }, + { // PutRequest + constructor: func() sigType { + req := new(PutRequest) + + amount := decimal.New(1) + req.SetAmount(amount) + + return req + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*PutRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + func(s sigType) { + req := s.(*PutRequest) + + mid := req.GetMessageID() + mid[0]++ + + req.SetMessageID(mid) + }, + func(s sigType) { + req := s.(*PutRequest) + + req.SetHeight(req.GetHeight() + 1) + }, + func(s sigType) { + req := s.(*PutRequest) + + amount := req.GetAmount() + if amount == nil { + req.SetAmount(decimal.New(0)) + } else { + req.SetAmount(amount.Add(decimal.New(amount.GetValue()))) + } + }, + }, + }, + { // ListRequest + constructor: func() sigType { + return new(ListRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*ListRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + }, + }, + { + constructor: func() sigType { + return new(DeleteRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*DeleteRequest) + + id, err := NewChequeID() + require.NoError(t, err) + + req.SetID(id) + }, + func(s sigType) { + req := s.(*DeleteRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + func(s sigType) { + req := s.(*DeleteRequest) + + mid := req.GetMessageID() + mid[0]++ + + req.SetMessageID(mid) + }, + }, + }, + } + + for _, item := range items { + { // token corruptions + v := item.constructor() + + token := new(service.Token) + v.SetToken(token) + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + token.SetSessionKey(append(token.GetSessionKey(), 1)) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + + { // payload corruptions + for _, corruption := range item.payloadCorrupt { + v := item.constructor() + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + corruption(v) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + } + } +} diff --git a/accounting/types.go b/accounting/types.go index 6a3b2e29..e16fa993 100644 --- a/accounting/types.go +++ b/accounting/types.go @@ -351,3 +351,103 @@ func (m *Settlement) Equal(s *Settlement) bool { } return len(m.Transactions) == 0 || reflect.DeepEqual(m.Transactions, s.Transactions) } + +// GetOwnerID is an OwnerID field getter. +func (m BalanceRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *BalanceRequest) SetOwnerID(owner OwnerID) { + m.OwnerID = owner +} + +// GetID is an ID field getter. +func (m GetRequest) GetID() ChequeID { + return m.ID +} + +// SetID is an ID field setter. +func (m *GetRequest) SetID(id ChequeID) { + m.ID = id +} + +// GetOwnerID is an OwnerID field getter. +func (m GetRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *GetRequest) SetOwnerID(id OwnerID) { + m.OwnerID = id +} + +// GetOwnerID is an OwnerID field getter. +func (m PutRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *PutRequest) SetOwnerID(id OwnerID) { + m.OwnerID = id +} + +// GetMessageID is a MessageID field getter. +func (m PutRequest) GetMessageID() MessageID { + return m.MessageID +} + +// SetMessageID is a MessageID field setter. +func (m *PutRequest) SetMessageID(id MessageID) { + m.MessageID = id +} + +// SetAmount is an Amount field setter. +func (m *PutRequest) SetAmount(amount *decimal.Decimal) { + m.Amount = amount +} + +// SetHeight is a Height field setter. +func (m *PutRequest) SetHeight(h uint64) { + m.Height = h +} + +// GetOwnerID is an OwnerID field getter. +func (m ListRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *ListRequest) SetOwnerID(id OwnerID) { + m.OwnerID = id +} + +// GetID is an ID field getter. +func (m DeleteRequest) GetID() ChequeID { + return m.ID +} + +// SetID is an ID field setter. +func (m *DeleteRequest) SetID(id ChequeID) { + m.ID = id +} + +// GetOwnerID is an OwnerID field getter. +func (m DeleteRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *DeleteRequest) SetOwnerID(id OwnerID) { + m.OwnerID = id +} + +// GetMessageID is a MessageID field getter. +func (m DeleteRequest) GetMessageID() MessageID { + return m.MessageID +} + +// SetMessageID is a MessageID field setter. +func (m *DeleteRequest) SetMessageID(id MessageID) { + m.MessageID = id +} diff --git a/accounting/types_test.go b/accounting/types_test.go index df81b46d..a440028f 100644 --- a/accounting/types_test.go +++ b/accounting/types_test.go @@ -84,3 +84,110 @@ func TestCheque(t *testing.T) { require.Equal(t, cheque.Amount, decimal.NewGAS(42)) }) } + +func TestBalanceRequest_SetOwnerID(t *testing.T) { + ownerID := OwnerID{1, 2, 3} + m := new(BalanceRequest) + + m.SetOwnerID(ownerID) + + require.Equal(t, ownerID, m.GetOwnerID()) +} + +func TestGetRequestGettersSetters(t *testing.T) { + t.Run("id", func(t *testing.T) { + id := ChequeID("test id") + m := new(GetRequest) + + m.SetID(id) + + require.Equal(t, id, m.GetID()) + }) + + t.Run("owner", func(t *testing.T) { + id := OwnerID{1, 2, 3} + m := new(GetRequest) + + m.SetOwnerID(id) + + require.Equal(t, id, m.GetOwnerID()) + }) +} + +func TestPutRequestGettersSetters(t *testing.T) { + t.Run("owner", func(t *testing.T) { + id := OwnerID{1, 2, 3} + m := new(PutRequest) + + m.SetOwnerID(id) + + require.Equal(t, id, m.GetOwnerID()) + }) + + t.Run("message ID", func(t *testing.T) { + id, err := refs.NewUUID() + require.NoError(t, err) + + m := new(PutRequest) + m.SetMessageID(id) + + require.Equal(t, id, m.GetMessageID()) + }) + + t.Run("amount", func(t *testing.T) { + amount := decimal.New(1) + m := new(PutRequest) + + m.SetAmount(amount) + + require.Equal(t, amount, m.GetAmount()) + }) + + t.Run("height", func(t *testing.T) { + h := uint64(3) + m := new(PutRequest) + + m.SetHeight(h) + + require.Equal(t, h, m.GetHeight()) + }) +} + +func TestListRequestGettersSetters(t *testing.T) { + ownerID := OwnerID{1, 2, 3} + m := new(ListRequest) + + m.SetOwnerID(ownerID) + + require.Equal(t, ownerID, m.GetOwnerID()) +} + +func TestDeleteRequestGettersSetters(t *testing.T) { + t.Run("id", func(t *testing.T) { + id := ChequeID("test id") + m := new(DeleteRequest) + + m.SetID(id) + + require.Equal(t, id, m.GetID()) + }) + + t.Run("owner", func(t *testing.T) { + id := OwnerID{1, 2, 3} + m := new(DeleteRequest) + + m.SetOwnerID(id) + + require.Equal(t, id, m.GetOwnerID()) + }) + + t.Run("message ID", func(t *testing.T) { + id, err := refs.NewUUID() + require.NoError(t, err) + + m := new(DeleteRequest) + m.SetMessageID(id) + + require.Equal(t, id, m.GetMessageID()) + }) +} diff --git a/bootstrap/sign.go b/bootstrap/sign.go new file mode 100644 index 00000000..34f7fc2c --- /dev/null +++ b/bootstrap/sign.go @@ -0,0 +1,46 @@ +package bootstrap + +import ( + "io" + + "github.com/nspcc-dev/neofs-api-go/service" +) + +// SignedData returns payload bytes of the request. +func (m Request) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m Request) SignedDataSize() (sz int) { + sz += m.GetType().Size() + + sz += m.GetState().Size() + + info := m.GetInfo() + sz += info.Size() + + return +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m Request) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetType().Bytes()) + + off += copy(p[off:], m.GetState().Bytes()) + + info := m.GetInfo() + // FIXME: implement and use stable functions + n, err := info.MarshalTo(p[off:]) + off += n + + return off, err +} diff --git a/bootstrap/sign_test.go b/bootstrap/sign_test.go new file mode 100644 index 00000000..2c76117a --- /dev/null +++ b/bootstrap/sign_test.go @@ -0,0 +1,82 @@ +package bootstrap + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestRequestSign(t *testing.T) { + sk := test.DecodeKey(0) + + type sigType interface { + service.SignedDataWithToken + service.SignKeyPairAccumulator + service.SignKeyPairSource + SetToken(*service.Token) + } + + items := []struct { + constructor func() sigType + payloadCorrupt []func(sigType) + }{ + { // Request + constructor: func() sigType { + return new(Request) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*Request) + + req.SetType(req.GetType() + 1) + }, + func(s sigType) { + req := s.(*Request) + + req.SetState(req.GetState() + 1) + }, + func(s sigType) { + req := s.(*Request) + + info := req.GetInfo() + info.Address += "1" + + req.SetInfo(info) + }, + }, + }, + } + + for _, item := range items { + { // token corruptions + v := item.constructor() + + token := new(service.Token) + v.SetToken(token) + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + token.SetSessionKey(append(token.GetSessionKey(), 1)) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + + { // payload corruptions + for _, corruption := range item.payloadCorrupt { + v := item.constructor() + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + corruption(v) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + } + } +} diff --git a/bootstrap/types.go b/bootstrap/types.go index 690d81c7..7ad3ec26 100644 --- a/bootstrap/types.go +++ b/bootstrap/types.go @@ -2,6 +2,7 @@ package bootstrap import ( "bytes" + "encoding/binary" "encoding/hex" "strconv" "strings" @@ -27,6 +28,8 @@ var ( _ proto.Message = (*SpreadMap)(nil) ) +var requestEndianness = binary.BigEndian + // Equals checks whether two NodeInfo has same address. func (m NodeInfo) Equals(n1 NodeInfo) bool { return m.Address == n1.Address && bytes.Equal(m.PubKey, n1.PubKey) @@ -98,3 +101,37 @@ func (m SpreadMap) String() string { ", " + "Netmap: [" + strings.Join(result, ",") + "]>" } + +// GetType is a Type field getter. +func (m Request) GetType() NodeType { + return m.Type +} + +// SetType is a Type field setter. +func (m *Request) SetType(t NodeType) { + m.Type = t +} + +// SetState is a State field setter. +func (m *Request) SetState(state Request_State) { + m.State = state +} + +// SetInfo is an Info field getter. +func (m *Request) SetInfo(info NodeInfo) { + m.Info = info +} + +// Size returns the size necessary for a binary representation of the state. +func (x Request_State) Size() int { + return 4 +} + +// Bytes returns a binary representation of the state. +func (x Request_State) Bytes() []byte { + data := make([]byte, x.Size()) + + requestEndianness.PutUint32(data, uint32(x)) + + return data +} diff --git a/bootstrap/types_test.go b/bootstrap/types_test.go new file mode 100644 index 00000000..20b1b1af --- /dev/null +++ b/bootstrap/types_test.go @@ -0,0 +1,39 @@ +package bootstrap + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRequestGettersSetters(t *testing.T) { + t.Run("type", func(t *testing.T) { + rt := NodeType(1) + m := new(Request) + + m.SetType(rt) + + require.Equal(t, rt, m.GetType()) + }) + + t.Run("state", func(t *testing.T) { + st := Request_State(1) + m := new(Request) + + m.SetState(st) + + require.Equal(t, st, m.GetState()) + }) + + t.Run("info", func(t *testing.T) { + info := NodeInfo{ + Address: "some address", + } + + m := new(Request) + + m.SetInfo(info) + + require.Equal(t, info, m.GetInfo()) + }) +} diff --git a/container/sign.go b/container/sign.go new file mode 100644 index 00000000..eafd93c3 --- /dev/null +++ b/container/sign.go @@ -0,0 +1,137 @@ +package container + +import ( + "encoding/binary" + "io" + + service "github.com/nspcc-dev/neofs-api-go/service" +) + +var requestEndianness = binary.BigEndian + +// SignedData returns payload bytes of the request. +func (m PutRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m PutRequest) SignedDataSize() (sz int) { + sz += m.GetMessageID().Size() + + sz += 8 + + sz += m.GetOwnerID().Size() + + rules := m.GetRules() + sz += rules.Size() + + sz += 4 + + return +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m PutRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetMessageID().Bytes()) + + requestEndianness.PutUint64(p[off:], m.GetCapacity()) + off += 8 + + off += copy(p[off:], m.GetOwnerID().Bytes()) + + rules := m.GetRules() + // FIXME: implement and use stable functions + n, err := rules.MarshalTo(p[off:]) + off += n + if err != nil { + return off, err + } + + requestEndianness.PutUint32(p[off:], m.GetBasicACL()) + off += 4 + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m DeleteRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m DeleteRequest) SignedDataSize() int { + return m.GetCID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m DeleteRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetCID().Bytes()) + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m GetRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m GetRequest) SignedDataSize() int { + return m.GetCID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetCID().Bytes()) + + return off, nil +} + +// SignedData returns payload bytes of the request. +func (m ListRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m ListRequest) SignedDataSize() int { + return m.GetOwnerID().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m ListRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetOwnerID().Bytes()) + + return off, nil +} diff --git a/container/sign_test.go b/container/sign_test.go new file mode 100644 index 00000000..e469399c --- /dev/null +++ b/container/sign_test.go @@ -0,0 +1,143 @@ +package container + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestRequestSign(t *testing.T) { + sk := test.DecodeKey(0) + + type sigType interface { + service.SignedDataWithToken + service.SignKeyPairAccumulator + service.SignKeyPairSource + SetToken(*service.Token) + } + + items := []struct { + constructor func() sigType + payloadCorrupt []func(sigType) + }{ + { // PutRequest + constructor: func() sigType { + return new(PutRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*PutRequest) + + id := req.GetMessageID() + id[0]++ + + req.SetMessageID(id) + }, + func(s sigType) { + req := s.(*PutRequest) + + req.SetCapacity(req.GetCapacity() + 1) + }, + func(s sigType) { + req := s.(*PutRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + func(s sigType) { + req := s.(*PutRequest) + + rules := req.GetRules() + rules.ReplFactor++ + + req.SetRules(rules) + }, + func(s sigType) { + req := s.(*PutRequest) + + req.SetBasicACL(req.GetBasicACL() + 1) + }, + }, + }, + { // DeleteRequest + constructor: func() sigType { + return new(DeleteRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*DeleteRequest) + + cid := req.GetCID() + cid[0]++ + + req.SetCID(cid) + }, + }, + }, + { // GetRequest + constructor: func() sigType { + return new(GetRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*GetRequest) + + cid := req.GetCID() + cid[0]++ + + req.SetCID(cid) + }, + }, + }, + { // ListRequest + constructor: func() sigType { + return new(ListRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*ListRequest) + + owner := req.GetOwnerID() + owner[0]++ + + req.SetOwnerID(owner) + }, + }, + }, + } + + for _, item := range items { + { // token corruptions + v := item.constructor() + + token := new(service.Token) + v.SetToken(token) + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + token.SetSessionKey(append(token.GetSessionKey(), 1)) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + + { // payload corruptions + for _, corruption := range item.payloadCorrupt { + v := item.constructor() + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + corruption(v) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + } + } +} diff --git a/container/types.go b/container/types.go index e358e6d6..f340aa59 100644 --- a/container/types.go +++ b/container/types.go @@ -93,3 +93,68 @@ func NewTestContainer() (*Container, error) { }, }) } + +// GetMessageID is a MessageID field getter. +func (m PutRequest) GetMessageID() MessageID { + return m.MessageID +} + +// SetMessageID is a MessageID field getter. +func (m *PutRequest) SetMessageID(id MessageID) { + m.MessageID = id +} + +// SetCapacity is a Capacity field setter. +func (m *PutRequest) SetCapacity(c uint64) { + m.Capacity = c +} + +// GetOwnerID is an OwnerID field getter. +func (m PutRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *PutRequest) SetOwnerID(owner OwnerID) { + m.OwnerID = owner +} + +// SetRules is a Rules field setter. +func (m *PutRequest) SetRules(rules netmap.PlacementRule) { + m.Rules = rules +} + +// SetBasicACL is a BasicACL field setter. +func (m *PutRequest) SetBasicACL(acl uint32) { + m.BasicACL = acl +} + +// GetCID is a CID field getter. +func (m DeleteRequest) GetCID() CID { + return m.CID +} + +// SetCID is a CID field setter. +func (m *DeleteRequest) SetCID(cid CID) { + m.CID = cid +} + +// GetCID is a CID field getter. +func (m GetRequest) GetCID() CID { + return m.CID +} + +// SetCID is a CID field setter. +func (m *GetRequest) SetCID(cid CID) { + m.CID = cid +} + +// GetOwnerID is an OwnerID field getter. +func (m ListRequest) GetOwnerID() OwnerID { + return m.OwnerID +} + +// SetOwnerID is an OwnerID field setter. +func (m *ListRequest) SetOwnerID(owner OwnerID) { + m.OwnerID = owner +} diff --git a/container/types_test.go b/container/types_test.go index fddccb36..cc171cbe 100644 --- a/container/types_test.go +++ b/container/types_test.go @@ -55,3 +55,88 @@ func TestCID(t *testing.T) { require.Equal(t, cid1, cid2) }) } + +func TestPutRequestGettersSetters(t *testing.T) { + t.Run("owner", func(t *testing.T) { + owner := OwnerID{1, 2, 3} + m := new(PutRequest) + + m.SetOwnerID(owner) + + require.Equal(t, owner, m.GetOwnerID()) + }) + + t.Run("capacity", func(t *testing.T) { + cp := uint64(3) + m := new(PutRequest) + + m.SetCapacity(cp) + + require.Equal(t, cp, m.GetCapacity()) + }) + + t.Run("message ID", func(t *testing.T) { + id, err := refs.NewUUID() + require.NoError(t, err) + + m := new(PutRequest) + + m.SetMessageID(id) + + require.Equal(t, id, m.GetMessageID()) + }) + + t.Run("rules", func(t *testing.T) { + rules := netmap.PlacementRule{ + ReplFactor: 1, + } + + m := new(PutRequest) + + m.SetRules(rules) + + require.Equal(t, rules, m.GetRules()) + }) + + t.Run("basic ACL", func(t *testing.T) { + bACL := uint32(5) + m := new(PutRequest) + + m.SetBasicACL(bACL) + + require.Equal(t, bACL, m.GetBasicACL()) + }) +} + +func TestDeleteRequestGettersSetters(t *testing.T) { + t.Run("cid", func(t *testing.T) { + cid := CID{1, 2, 3} + m := new(DeleteRequest) + + m.SetCID(cid) + + require.Equal(t, cid, m.GetCID()) + }) +} + +func TestGetRequestGettersSetters(t *testing.T) { + t.Run("cid", func(t *testing.T) { + cid := CID{1, 2, 3} + m := new(GetRequest) + + m.SetCID(cid) + + require.Equal(t, cid, m.GetCID()) + }) +} + +func TestListRequestGettersSetters(t *testing.T) { + t.Run("owner", func(t *testing.T) { + owner := OwnerID{1, 2, 3} + m := new(PutRequest) + + m.SetOwnerID(owner) + + require.Equal(t, owner, m.GetOwnerID()) + }) +} diff --git a/object/sign.go b/object/sign.go index 25d0b2f4..1ed3efa2 100644 --- a/object/sign.go +++ b/object/sign.go @@ -3,34 +3,27 @@ package object import ( "encoding/binary" "io" + + "github.com/nspcc-dev/neofs-api-go/service" ) // SignedData returns payload bytes of the request. // // If payload is nil, ErrHeaderNotFound returns. func (m PutRequest) SignedData() ([]byte, error) { - sz := m.SignedDataSize() - if sz < 0 { - return nil, ErrHeaderNotFound - } - - data := make([]byte, sz) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m PutRequest) ReadSignedData(p []byte) error { +func (m PutRequest) ReadSignedData(p []byte) (int, error) { r := m.GetR() if r == nil { - return ErrHeaderNotFound + return 0, ErrHeaderNotFound } - _, err := r.MarshalTo(p) - - return err + return r.MarshalTo(p) } // SignedDataSize returns the size of payload of the Put request. @@ -47,26 +40,26 @@ func (m PutRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m GetRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m GetRequest) ReadSignedData(p []byte) error { +func (m GetRequest) ReadSignedData(p []byte) (int, error) { addr := m.GetAddress() if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } - off := copy(p, addr.CID.Bytes()) + var off int - copy(p[off:], addr.ObjectID.Bytes()) + off += copy(p[off:], addr.CID.Bytes()) - return nil + off += copy(p[off:], addr.ObjectID.Bytes()) + + return off, nil } // SignedDataSize returns payload size of the request. @@ -76,28 +69,28 @@ func (m GetRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m HeadRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m HeadRequest) ReadSignedData(p []byte) error { +func (m HeadRequest) ReadSignedData(p []byte) (int, error) { if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } if m.GetFullHeaders() { p[0] = 1 } - off := 1 + copy(p[1:], m.Address.CID.Bytes()) + off := 1 - copy(p[off:], m.Address.ObjectID.Bytes()) + off += copy(p[off:], m.Address.CID.Bytes()) - return nil + off += copy(p[off:], m.Address.ObjectID.Bytes()) + + return off, nil } // SignedDataSize returns payload size of the request. @@ -107,24 +100,24 @@ func (m HeadRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m DeleteRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m DeleteRequest) ReadSignedData(p []byte) error { +func (m DeleteRequest) ReadSignedData(p []byte) (int, error) { if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } - off := copy(p, m.OwnerID.Bytes()) + var off int - copy(p[off:], addressBytes(m.Address)) + off += copy(p[off:], m.OwnerID.Bytes()) - return nil + off += copy(p[off:], addressBytes(m.Address)) + + return off, nil } // SignedDataSize returns payload size of the request. @@ -134,27 +127,25 @@ func (m DeleteRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m GetRangeRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m GetRangeRequest) ReadSignedData(p []byte) error { +func (m GetRangeRequest) ReadSignedData(p []byte) (int, error) { if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } n, err := (&m.Range).MarshalTo(p) if err != nil { - return err + return 0, err } - copy(p[n:], addressBytes(m.GetAddress())) + n += copy(p[n:], addressBytes(m.GetAddress())) - return nil + return n, nil } // SignedDataSize returns payload size of the request. @@ -164,17 +155,15 @@ func (m GetRangeRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m GetRangeHashRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m GetRangeHashRequest) ReadSignedData(p []byte) error { +func (m GetRangeHashRequest) ReadSignedData(p []byte) (int, error) { if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } var off int @@ -185,7 +174,7 @@ func (m GetRangeHashRequest) ReadSignedData(p []byte) error { off += copy(p[off:], m.GetSalt()) - return nil + return off, nil } // SignedDataSize returns payload size of the request. @@ -203,17 +192,15 @@ func (m GetRangeHashRequest) SignedDataSize() int { // SignedData returns payload bytes of the request. func (m SearchRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - return data, m.ReadSignedData(data) + return service.SignedDataFromReader(m) } // ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. -func (m SearchRequest) ReadSignedData(p []byte) error { +func (m SearchRequest) ReadSignedData(p []byte) (int, error) { if len(p) < m.SignedDataSize() { - return io.ErrUnexpectedEOF + return 0, io.ErrUnexpectedEOF } var off int @@ -223,9 +210,9 @@ func (m SearchRequest) ReadSignedData(p []byte) error { binary.BigEndian.PutUint32(p[off:], m.GetQueryVersion()) off += 4 - copy(p[off:], m.GetQuery()) + off += copy(p[off:], m.GetQuery()) - return nil + return off, nil } // SignedDataSize returns payload size of the request. diff --git a/service/errors.go b/service/errors.go index 6241ad2c..f3a0dfc0 100644 --- a/service/errors.go +++ b/service/errors.go @@ -43,3 +43,7 @@ const ErrNilDataWithTokenSignAccumulator = internal.Error("signed data with toke // 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") + +// ErrNilSignedDataReader is returned by functions that expect +// a non-nil SignedDataReader, but received nil. +const ErrNilSignedDataReader = internal.Error("signed data reader is nil") diff --git a/service/role.go b/service/role.go index 4c405c12..64a00744 100644 --- a/service/role.go +++ b/service/role.go @@ -1,5 +1,7 @@ package service +import "encoding/binary" + const ( _ NodeRole = iota // InnerRingNode that work like IR node. @@ -19,3 +21,17 @@ func (nt NodeRole) String() string { return "Unknown" } } + +// Size returns the size necessary for a binary representation of the NodeRole. +func (nt NodeRole) Size() int { + return 4 +} + +// Bytes returns a binary representation of the NodeRole. +func (nt NodeRole) Bytes() []byte { + data := make([]byte, nt.Size()) + + binary.BigEndian.PutUint32(data, uint32(nt)) + + return data +} diff --git a/service/token.go b/service/token.go index 78fccfac..32c390fc 100644 --- a/service/token.go +++ b/service/token.go @@ -123,11 +123,7 @@ func (m *Token) AddSignKey(sig []byte, _ *ecdsa.PublicKey) { // SignedData returns token information in a binary representation. func (m *Token) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - copyTokenSignedData(data, m) - - return data, nil + return SignedDataFromReader(m) } // ReadSignedData copies a binary representation of the token information to passed buffer. diff --git a/service/utils.go b/service/utils.go new file mode 100644 index 00000000..17b23bb0 --- /dev/null +++ b/service/utils.go @@ -0,0 +1,18 @@ +package service + +// SignedDataFromReader allocates buffer and reads bytes from passed reader to it. +// +// If passed SignedDataReader is nil, ErrNilSignedDataReader returns. +func SignedDataFromReader(r SignedDataReader) ([]byte, error) { + if r == nil { + return nil, ErrNilSignedDataReader + } + + data := make([]byte, r.SignedDataSize()) + + if _, err := r.ReadSignedData(data); err != nil { + return nil, err + } + + return data, nil +} diff --git a/service/utils_test.go b/service/utils_test.go new file mode 100644 index 00000000..60a2352d --- /dev/null +++ b/service/utils_test.go @@ -0,0 +1,34 @@ +package service + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestSignedDataFromReader(t *testing.T) { + // nil SignedDataReader + _, err := SignedDataFromReader(nil) + require.EqualError(t, err, ErrNilSignedDataReader.Error()) + + rdr := &testSignedDataReader{ + testSignedDataSrc: new(testSignedDataSrc), + } + + // make reader to return an error + rdr.err = errors.New("test error") + + _, err = SignedDataFromReader(rdr) + require.EqualError(t, err, rdr.err.Error()) + + // remove the error + rdr.err = nil + + // fill the data + rdr.data = testData(t, 10) + + res, err := SignedDataFromReader(rdr) + require.NoError(t, err) + require.Equal(t, rdr.data, res) +} diff --git a/session/request.go b/session/request.go index 0bb51762..73c05e59 100644 --- a/session/request.go +++ b/session/request.go @@ -5,6 +5,7 @@ import ( "io" "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-api-go/service" ) const signedRequestDataSize = 0 + @@ -31,14 +32,7 @@ func (m *CreateRequest) SetOwnerID(id OwnerID) { // SignedData returns payload bytes of the request. func (m CreateRequest) SignedData() ([]byte, error) { - data := make([]byte, m.SignedDataSize()) - - _, err := m.ReadSignedData(data) - if err != nil { - return nil, err - } - - return data, nil + return service.SignedDataFromReader(m) } // SignedDataSize returns payload size of the request. diff --git a/state/sign.go b/state/sign.go new file mode 100644 index 00000000..88f038c1 --- /dev/null +++ b/state/sign.go @@ -0,0 +1,67 @@ +package state + +import ( + "io" + + "github.com/nspcc-dev/neofs-api-go/service" +) + +// SignedData returns payload bytes of the request. +// +// Always returns an empty slice. +func (m NetmapRequest) SignedData() ([]byte, error) { + return make([]byte, 0), nil +} + +// SignedData returns payload bytes of the request. +// +// Always returns an empty slice. +func (m MetricsRequest) SignedData() ([]byte, error) { + return make([]byte, 0), nil +} + +// SignedData returns payload bytes of the request. +// +// Always returns an empty slice. +func (m HealthRequest) SignedData() ([]byte, error) { + return make([]byte, 0), nil +} + +// SignedData returns payload bytes of the request. +// +// Always returns an empty slice. +func (m DumpRequest) SignedData() ([]byte, error) { + return make([]byte, 0), nil +} + +// SignedData returns payload bytes of the request. +// +// Always returns an empty slice. +func (m DumpVarsRequest) SignedData() ([]byte, error) { + return make([]byte, 0), nil +} + +// SignedData returns payload bytes of the request. +func (m ChangeStateRequest) SignedData() ([]byte, error) { + return service.SignedDataFromReader(m) +} + +// SignedDataSize returns payload size of the request. +func (m ChangeStateRequest) SignedDataSize() int { + return m.GetState().Size() +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the Request size is insufficient, io.ErrUnexpectedEOF returns. +func (m ChangeStateRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], m.GetState().Bytes()) + + return off, nil +} diff --git a/state/sign_test.go b/state/sign_test.go new file mode 100644 index 00000000..9b2bca90 --- /dev/null +++ b/state/sign_test.go @@ -0,0 +1,94 @@ +package state + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestRequestSign(t *testing.T) { + sk := test.DecodeKey(0) + + type sigType interface { + service.SignedDataWithToken + service.SignKeyPairAccumulator + service.SignKeyPairSource + SetToken(*service.Token) + } + + items := []struct { + constructor func() sigType + payloadCorrupt []func(sigType) + }{ + { // NetmapRequest + constructor: func() sigType { + return new(NetmapRequest) + }, + }, + { // MetricsRequest + constructor: func() sigType { + return new(MetricsRequest) + }, + }, + { // HealthRequest + constructor: func() sigType { + return new(HealthRequest) + }, + }, + { // DumpRequest + constructor: func() sigType { + return new(DumpRequest) + }, + }, + { // DumpVarsRequest + constructor: func() sigType { + return new(DumpVarsRequest) + }, + }, + { + constructor: func() sigType { + return new(ChangeStateRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + req := s.(*ChangeStateRequest) + + req.SetState(req.GetState() + 1) + }, + }, + }, + } + + for _, item := range items { + { // token corruptions + v := item.constructor() + + token := new(service.Token) + v.SetToken(token) + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + token.SetSessionKey(append(token.GetSessionKey(), 1)) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + + { // payload corruptions + for _, corruption := range item.payloadCorrupt { + v := item.constructor() + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + corruption(v) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + } + } +} diff --git a/state/types.go b/state/types.go new file mode 100644 index 00000000..6b572db6 --- /dev/null +++ b/state/types.go @@ -0,0 +1,24 @@ +package state + +import ( + "encoding/binary" +) + +// SetState is a State field setter. +func (m *ChangeStateRequest) SetState(st ChangeStateRequest_State) { + m.State = st +} + +// Size returns the size of the state binary representation. +func (ChangeStateRequest_State) Size() int { + return 4 +} + +// Bytes returns the state binary representation. +func (x ChangeStateRequest_State) Bytes() []byte { + data := make([]byte, x.Size()) + + binary.BigEndian.PutUint32(data, uint32(x)) + + return data +} diff --git a/state/types_test.go b/state/types_test.go new file mode 100644 index 00000000..5d5f5de3 --- /dev/null +++ b/state/types_test.go @@ -0,0 +1,18 @@ +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestChangeStateRequestGettersSetters(t *testing.T) { + t.Run("state", func(t *testing.T) { + st := ChangeStateRequest_State(1) + m := new(ChangeStateRequest) + + m.SetState(st) + + require.Equal(t, st, m.GetState()) + }) +}