From fc177c4ce3828b56bb7eb4f562629ccd12b4355d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 13:04:10 +0300 Subject: [PATCH 01/19] service: change constant errors This commit: * moves defined errors to a separate file; * renames ErrEmptyToken to ErrNilToken; * merges ErrZeroTTL and ErrIncorrectTTL into single ErrInvalidTTL. --- service/errors.go | 18 ++++++++++++++++++ service/meta.go | 13 ++----------- service/meta_test.go | 4 ++-- service/token.go | 12 ++++-------- service/token_test.go | 4 ++-- service/verify.go | 17 +++-------------- 6 files changed, 31 insertions(+), 37 deletions(-) create mode 100644 service/errors.go diff --git a/service/errors.go b/service/errors.go new file mode 100644 index 00000000..4aefb4eb --- /dev/null +++ b/service/errors.go @@ -0,0 +1,18 @@ +package service + +import "github.com/nspcc-dev/neofs-api-go/internal" + +// ErrNilToken is returned by functions that expect a non-nil token argument, but received nil. +const ErrNilToken = internal.Error("token is nil") + +// ErrInvalidTTL means that the TTL value does not satisfy a specific criterion. +const ErrInvalidTTL = internal.Error("invalid TTL value") + +// ErrInvalidPublicKeyBytes means that the public key could not be unmarshaled. +const ErrInvalidPublicKeyBytes = internal.Error("cannot load public key") + +// ErrCannotFindOwner is raised when signatures empty in GetOwner. +const ErrCannotFindOwner = internal.Error("cannot find owner public key") + +// ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey +const ErrWrongOwner = internal.Error("wrong owner") diff --git a/service/meta.go b/service/meta.go index 8602dca7..ea1a83d6 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,7 +1,6 @@ package service import ( - "github.com/nspcc-dev/neofs-api-go/internal" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -63,14 +62,6 @@ const ( SingleForwardingTTL ) -const ( - // ErrZeroTTL is raised when zero ttl is passed. - ErrZeroTTL = internal.Error("zero ttl") - - // ErrIncorrectTTL is raised when NonForwardingTTL is passed and NodeRole != InnerRingNode. - ErrIncorrectTTL = internal.Error("incorrect ttl") -) - // SetVersion sets protocol version to ResponseMetaHeader. func (m *ResponseMetaHeader) SetVersion(v uint32) { m.Version = v } @@ -105,7 +96,7 @@ func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v } func IRNonForwarding(role NodeRole) TTLCondition { return func(ttl uint32) error { if ttl == NonForwardingTTL && role != InnerRingNode { - return ErrIncorrectTTL + return ErrInvalidTTL } return nil @@ -117,7 +108,7 @@ func ProcessRequestTTL(req MetaHeader, cond ...TTLCondition) error { ttl := req.GetTTL() if ttl == ZeroTTL { - return status.New(codes.InvalidArgument, ErrZeroTTL.Error()).Err() + return status.New(codes.InvalidArgument, ErrInvalidTTL.Error()).Err() } for i := range cond { diff --git a/service/meta_test.go b/service/meta_test.go index 388b6ce5..de77ac81 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -26,13 +26,13 @@ func TestMetaRequest(t *testing.T) { }, { code: codes.InvalidArgument, - msg: ErrIncorrectTTL.Error(), + msg: ErrInvalidTTL.Error(), name: "direct to storage node", handler: IRNonForwarding(StorageNode), RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL}, }, { - msg: ErrZeroTTL.Error(), + msg: ErrInvalidTTL.Error(), code: codes.InvalidArgument, name: "zero ttl", handler: IRNonForwarding(StorageNode), diff --git a/service/token.go b/service/token.go index 077e672b..ece44c26 100644 --- a/service/token.go +++ b/service/token.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "encoding/binary" - "github.com/nspcc-dev/neofs-api-go/internal" "github.com/nspcc-dev/neofs-api-go/refs" crypto "github.com/nspcc-dev/neofs-crypto" ) @@ -62,9 +61,6 @@ type SessionToken interface { SignatureContainer } -// ErrEmptyToken is raised when passed Token is nil. -const ErrEmptyToken = internal.Error("token is empty") - var _ SessionToken = (*Token)(nil) var tokenEndianness = binary.BigEndian @@ -183,11 +179,11 @@ func verificationTokenData(token SessionToken) []byte { // SignToken calculates and stores the signature of token information. // -// If passed token is nil, ErrEmptyToken returns. +// If passed token is nil, ErrNilToken returns. // If passed private key is nil, crypto.ErrEmptyPrivateKey returns. func SignToken(token SessionToken, key *ecdsa.PrivateKey) error { if token == nil { - return ErrEmptyToken + return ErrNilToken } else if key == nil { return crypto.ErrEmptyPrivateKey } @@ -204,11 +200,11 @@ func SignToken(token SessionToken, key *ecdsa.PrivateKey) error { // VerifyTokenSignature checks if token was signed correctly. // -// If passed token is nil, ErrEmptyToken returns. +// If passed token is nil, ErrNilToken returns. // If passed public key is nil, crypto.ErrEmptyPublicKey returns. func VerifyTokenSignature(token SessionToken, key *ecdsa.PublicKey) error { if token == nil { - return ErrEmptyToken + return ErrNilToken } else if key == nil { return crypto.ErrEmptyPublicKey } diff --git a/service/token_test.go b/service/token_test.go index 0b28084e..1e02f468 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -93,12 +93,12 @@ func TestSignToken(t *testing.T) { // nil token require.EqualError(t, SignToken(nil, nil), - ErrEmptyToken.Error(), + ErrNilToken.Error(), ) require.EqualError(t, VerifyTokenSignature(nil, nil), - ErrEmptyToken.Error(), + ErrNilToken.Error(), ) var token SessionToken = new(Token) diff --git a/service/verify.go b/service/verify.go index 182685d5..7ac3cf30 100644 --- a/service/verify.go +++ b/service/verify.go @@ -35,17 +35,6 @@ type ( } ) -const ( - // ErrCannotLoadPublicKey is raised when cannot unmarshal public key from RequestVerificationHeader_Sign. - ErrCannotLoadPublicKey = internal.Error("cannot load public key") - - // ErrCannotFindOwner is raised when signatures empty in GetOwner. - ErrCannotFindOwner = internal.Error("cannot find owner public key") - - // ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey - ErrWrongOwner = internal.Error("wrong owner") -) - // SetSignatures replaces signatures stored in RequestVerificationHeader. func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificationHeader_Signature) { m.Signatures = signatures @@ -81,7 +70,7 @@ func (m *RequestVerificationHeader) GetOwner() (*ecdsa.PublicKey, error) { return key, nil } - return nil, ErrCannotLoadPublicKey + return nil, ErrInvalidPublicKeyBytes } // GetLastPeer tries to get last peer public key from signatures. @@ -99,7 +88,7 @@ func (m *RequestVerificationHeader) GetLastPeer() (*ecdsa.PublicKey, error) { return key, nil } - return nil, ErrCannotLoadPublicKey + return nil, ErrInvalidPublicKeyBytes } } @@ -190,7 +179,7 @@ func VerifyRequestHeader(msg VerifiableRequest) error { key := crypto.UnmarshalPublicKey(peer) if key == nil { - return errors.Wrapf(ErrCannotLoadPublicKey, "%d: %02x", i, peer) + return errors.Wrapf(ErrInvalidPublicKeyBytes, "%d: %02x", i, peer) } if size := msg.Size(); size <= cap(data) { From b785eb710a157cea40c85d7c993826c43e830584 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 13:38:27 +0300 Subject: [PATCH 02/19] service: transfer TTL code to a separate file --- service/meta.go | 67 +----------------------------- service/meta_test.go | 83 ------------------------------------- service/ttl.go | 73 ++++++++++++++++++++++++++++++++ service/ttl_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 148 deletions(-) create mode 100644 service/ttl.go create mode 100644 service/ttl_test.go diff --git a/service/meta.go b/service/meta.go index ea1a83d6..2675b79e 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,11 +1,5 @@ package service -import ( - "github.com/pkg/errors" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - type ( // MetaHeader contains meta information of request. // It provides methods to get or set meta information meta header. @@ -15,9 +9,8 @@ type ( ResetMeta() RequestMetaHeader RestoreMeta(RequestMetaHeader) - // TTLRequest to verify and update ttl requests. - GetTTL() uint32 - SetTTL(uint32) + // TTLHeader allows to get and set TTL value of request. + TTLHeader // EpochHeader gives possibility to get or set epoch in RPC Requests. EpochHeader @@ -46,20 +39,6 @@ type ( GetRaw() bool SetRaw(bool) } - - // TTLCondition is closure, that allows to validate request with ttl. - TTLCondition func(ttl uint32) error -) - -const ( - // ZeroTTL is empty ttl, should produce ErrZeroTTL. - ZeroTTL = iota - - // NonForwardingTTL is a ttl that allows direct connections only. - NonForwardingTTL - - // SingleForwardingTTL is a ttl that allows connections through another node. - SingleForwardingTTL ) // SetVersion sets protocol version to ResponseMetaHeader. @@ -71,9 +50,6 @@ func (m *ResponseMetaHeader) SetEpoch(v uint64) { m.Epoch = v } // SetVersion sets protocol version to RequestMetaHeader. func (m *RequestMetaHeader) SetVersion(v uint32) { m.Version = v } -// SetTTL sets TTL to RequestMetaHeader. -func (m *RequestMetaHeader) SetTTL(v uint32) { m.TTL = v } - // SetEpoch sets Epoch to RequestMetaHeader. func (m *RequestMetaHeader) SetEpoch(v uint64) { m.Epoch = v } @@ -91,42 +67,3 @@ func (m *RequestMetaHeader) ResetMeta() RequestMetaHeader { // RestoreMeta sets current RequestMetaHeader to passed value. func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v } - -// IRNonForwarding condition that allows NonForwardingTTL only for IR -func IRNonForwarding(role NodeRole) TTLCondition { - return func(ttl uint32) error { - if ttl == NonForwardingTTL && role != InnerRingNode { - return ErrInvalidTTL - } - - return nil - } -} - -// ProcessRequestTTL validates and update ttl requests. -func ProcessRequestTTL(req MetaHeader, cond ...TTLCondition) error { - ttl := req.GetTTL() - - if ttl == ZeroTTL { - return status.New(codes.InvalidArgument, ErrInvalidTTL.Error()).Err() - } - - for i := range cond { - if cond[i] == nil { - continue - } - - // check specific condition: - if err := cond[i](ttl); err != nil { - if st, ok := status.FromError(errors.Cause(err)); ok { - return st.Err() - } - - return status.New(codes.InvalidArgument, err.Error()).Err() - } - } - - req.SetTTL(ttl - 1) - - return nil -} diff --git a/service/meta_test.go b/service/meta_test.go index de77ac81..fb7fb171 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -3,92 +3,9 @@ package service import ( "testing" - "github.com/pkg/errors" "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) -type mockedRequest struct { - msg string - name string - code codes.Code - handler TTLCondition - RequestMetaHeader -} - -func TestMetaRequest(t *testing.T) { - tests := []mockedRequest{ - { - name: "direct to ir node", - handler: IRNonForwarding(InnerRingNode), - RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL}, - }, - { - code: codes.InvalidArgument, - msg: ErrInvalidTTL.Error(), - name: "direct to storage node", - handler: IRNonForwarding(StorageNode), - RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL}, - }, - { - msg: ErrInvalidTTL.Error(), - code: codes.InvalidArgument, - name: "zero ttl", - handler: IRNonForwarding(StorageNode), - RequestMetaHeader: RequestMetaHeader{TTL: ZeroTTL}, - }, - { - name: "default to ir node", - handler: IRNonForwarding(InnerRingNode), - RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, - }, - { - name: "default to storage node", - handler: IRNonForwarding(StorageNode), - RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, - }, - { - msg: "not found", - code: codes.NotFound, - name: "custom status error", - RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, - handler: func(_ uint32) error { return status.Error(codes.NotFound, "not found") }, - }, - { - msg: "not found", - code: codes.NotFound, - name: "custom wrapped status error", - RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, - handler: func(_ uint32) error { - err := status.Error(codes.NotFound, "not found") - err = errors.Wrap(err, "some error context") - err = errors.Wrap(err, "another error context") - return err - }, - }, - } - - for i := range tests { - tt := tests[i] - t.Run(tt.name, func(t *testing.T) { - before := tt.GetTTL() - err := ProcessRequestTTL(&tt, tt.handler) - if tt.msg != "" { - require.Errorf(t, err, tt.msg) - - state, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, tt.code, state.Code()) - require.Equal(t, tt.msg, state.Message()) - } else { - require.NoError(t, err) - require.NotEqualf(t, before, tt.GetTTL(), "ttl should be changed: %d vs %d", before, tt.GetTTL()) - } - }) - } -} - func TestRequestMetaHeader_SetEpoch(t *testing.T) { m := new(ResponseMetaHeader) epoch := uint64(3) diff --git a/service/ttl.go b/service/ttl.go new file mode 100644 index 00000000..f069f547 --- /dev/null +++ b/service/ttl.go @@ -0,0 +1,73 @@ +package service + +import ( + "github.com/pkg/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// TTLHeader is an interface of the container of a numerical TTL value. +type TTLHeader interface { + GetTTL() uint32 + SetTTL(uint32) +} + +// TTLCondition is a function type that used to verify that TTL value match a specific criterion. +// Nil error indicates compliance with the criterion. +type TTLCondition func(ttl uint32) error + +// TTL constants. +const ( + // ZeroTTL is an upper bound of invalid TTL values. + ZeroTTL = iota + + // NonForwardingTTL is a TTL value that does not imply a request forwarding. + NonForwardingTTL + + // SingleForwardingTTL is a TTL value that imply potential forwarding with NonForwardingTTL. + SingleForwardingTTL +) + +// SetTTL is a TTL field setter. +func (m *RequestMetaHeader) SetTTL(v uint32) { + m.TTL = v +} + +// IRNonForwarding condition that allows NonForwardingTTL only for IR. +func IRNonForwarding(role NodeRole) TTLCondition { + return func(ttl uint32) error { + if ttl == NonForwardingTTL && role != InnerRingNode { + return ErrInvalidTTL + } + + return nil + } +} + +// ProcessRequestTTL validates and updates requests with TTL. +func ProcessRequestTTL(req TTLHeader, cond ...TTLCondition) error { + ttl := req.GetTTL() + + if ttl == ZeroTTL { + return status.New(codes.InvalidArgument, ErrInvalidTTL.Error()).Err() + } + + for i := range cond { + if cond[i] == nil { + continue + } + + // check specific condition: + if err := cond[i](ttl); err != nil { + if st, ok := status.FromError(errors.Cause(err)); ok { + return st.Err() + } + + return status.New(codes.InvalidArgument, err.Error()).Err() + } + } + + req.SetTTL(ttl - 1) + + return nil +} diff --git a/service/ttl_test.go b/service/ttl_test.go new file mode 100644 index 00000000..1c982f55 --- /dev/null +++ b/service/ttl_test.go @@ -0,0 +1,99 @@ +package service + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type mockedRequest struct { + msg string + name string + code codes.Code + handler TTLCondition + RequestMetaHeader +} + +func TestMetaRequest(t *testing.T) { + tests := []mockedRequest{ + { + name: "direct to ir node", + handler: IRNonForwarding(InnerRingNode), + RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL}, + }, + { + code: codes.InvalidArgument, + msg: ErrInvalidTTL.Error(), + name: "direct to storage node", + handler: IRNonForwarding(StorageNode), + RequestMetaHeader: RequestMetaHeader{TTL: NonForwardingTTL}, + }, + { + msg: ErrInvalidTTL.Error(), + code: codes.InvalidArgument, + name: "zero ttl", + handler: IRNonForwarding(StorageNode), + RequestMetaHeader: RequestMetaHeader{TTL: ZeroTTL}, + }, + { + name: "default to ir node", + handler: IRNonForwarding(InnerRingNode), + RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, + }, + { + name: "default to storage node", + handler: IRNonForwarding(StorageNode), + RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, + }, + { + msg: "not found", + code: codes.NotFound, + name: "custom status error", + RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, + handler: func(_ uint32) error { return status.Error(codes.NotFound, "not found") }, + }, + { + msg: "not found", + code: codes.NotFound, + name: "custom wrapped status error", + RequestMetaHeader: RequestMetaHeader{TTL: SingleForwardingTTL}, + handler: func(_ uint32) error { + err := status.Error(codes.NotFound, "not found") + err = errors.Wrap(err, "some error context") + err = errors.Wrap(err, "another error context") + return err + }, + }, + } + + for i := range tests { + tt := tests[i] + t.Run(tt.name, func(t *testing.T) { + before := tt.GetTTL() + err := ProcessRequestTTL(&tt, tt.handler) + if tt.msg != "" { + require.Errorf(t, err, tt.msg) + + state, ok := status.FromError(err) + require.True(t, ok) + require.Equal(t, tt.code, state.Code()) + require.Equal(t, tt.msg, state.Message()) + } else { + require.NoError(t, err) + require.NotEqualf(t, before, tt.GetTTL(), "ttl should be changed: %d vs %d", before, tt.GetTTL()) + } + }) + } +} + +func TestRequestMetaHeader_SetTTL(t *testing.T) { + m := new(RequestMetaHeader) + ttl := uint32(3) + + m.SetTTL(ttl) + + require.Equal(t, ttl, m.GetTTL()) +} From 8270245455f013f55c4081988ca64408301e429d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 14:00:25 +0300 Subject: [PATCH 03/19] service: transfer public types to a separate file --- service/alias.go | 12 +++- service/role.go | 3 - service/token.go | 56 ----------------- service/ttl.go | 4 -- service/types.go | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 66 deletions(-) create mode 100644 service/types.go diff --git a/service/alias.go b/service/alias.go index 6c22ecef..9a407027 100644 --- a/service/alias.go +++ b/service/alias.go @@ -4,11 +4,17 @@ import ( "github.com/nspcc-dev/neofs-api-go/refs" ) -// TokenID is type alias of UUID ref. +// TokenID is a type alias of UUID ref. type TokenID = refs.UUID -// OwnerID is type alias of OwnerID ref. +// OwnerID is a type alias of OwnerID ref. type OwnerID = refs.OwnerID -// Address is type alias of Address ref. +// Address is a type alias of Address ref. type Address = refs.Address + +// AddressContainer is a type alias of refs.AddressContainer. +type AddressContainer = refs.AddressContainer + +// OwnerIDContainer is a type alias of refs.OwnerIDContainer. +type OwnerIDContainer = refs.OwnerIDContainer diff --git a/service/role.go b/service/role.go index 53bcdf55..4c405c12 100644 --- a/service/role.go +++ b/service/role.go @@ -1,8 +1,5 @@ package service -// NodeRole to identify in Bootstrap service. -type NodeRole int32 - const ( _ NodeRole = iota // InnerRingNode that work like IR node. diff --git a/service/token.go b/service/token.go index ece44c26..2aa159b4 100644 --- a/service/token.go +++ b/service/token.go @@ -4,65 +4,9 @@ import ( "crypto/ecdsa" "encoding/binary" - "github.com/nspcc-dev/neofs-api-go/refs" crypto "github.com/nspcc-dev/neofs-crypto" ) -// VerbContainer is an interface of the container of a token verb value. -type VerbContainer interface { - GetVerb() Token_Info_Verb - SetVerb(Token_Info_Verb) -} - -// TokenIDContainer is an interface of the container of a token ID value. -type TokenIDContainer interface { - GetID() TokenID - SetID(TokenID) -} - -// CreationEpochContainer is an interface of the container of a creation epoch number. -type CreationEpochContainer interface { - CreationEpoch() uint64 - SetCreationEpoch(uint64) -} - -// ExpirationEpochContainer is an interface of the container of an expiration epoch number. -type ExpirationEpochContainer interface { - ExpirationEpoch() uint64 - SetExpirationEpoch(uint64) -} - -// SessionKeyContainer is an interface of the container of session key bytes. -type SessionKeyContainer interface { - GetSessionKey() []byte - SetSessionKey([]byte) -} - -// SignatureContainer is an interface of the container of signature bytes. -type SignatureContainer interface { - GetSignature() []byte - SetSignature([]byte) -} - -// SessionTokenInfo is an interface that determines the information scope of session token. -type SessionTokenInfo interface { - TokenIDContainer - refs.OwnerIDContainer - VerbContainer - refs.AddressContainer - CreationEpochContainer - ExpirationEpochContainer - SessionKeyContainer -} - -// SessionToken is an interface of token information and signature pair. -type SessionToken interface { - SessionTokenInfo - SignatureContainer -} - -var _ SessionToken = (*Token)(nil) - var tokenEndianness = binary.BigEndian // GetID is an ID field getter. diff --git a/service/ttl.go b/service/ttl.go index f069f547..c79cc85e 100644 --- a/service/ttl.go +++ b/service/ttl.go @@ -12,10 +12,6 @@ type TTLHeader interface { SetTTL(uint32) } -// TTLCondition is a function type that used to verify that TTL value match a specific criterion. -// Nil error indicates compliance with the criterion. -type TTLCondition func(ttl uint32) error - // TTL constants. const ( // ZeroTTL is an upper bound of invalid TTL values. diff --git a/service/types.go b/service/types.go new file mode 100644 index 00000000..cad4b3c4 --- /dev/null +++ b/service/types.go @@ -0,0 +1,161 @@ +package service + +// NodeRole to identify in Bootstrap service. +type NodeRole int32 + +// TTLCondition is a function type that used to verify that TTL values match a specific criterion. +// Nil error indicates compliance with the criterion. +type TTLCondition func(uint32) error + +// RawSource is an interface of the container of a boolean Raw value with read access. +type RawSource interface { + GetRaw() bool +} + +// RawContainer is an interface of the container of a boolean Raw value. +type RawContainer interface { + RawSource + SetRaw(bool) +} + +// VersionContainer is an interface of the container of a numerical Version value with read access. +type VersionSource interface { + GetVersion() uint32 +} + +// VersionContainer is an interface of the container of a numerical Version value. +type VersionContainer interface { + VersionSource + SetVersion(uint32) +} + +// EpochSource is an interface of the container of a NeoFS epoch number with read access. +type EpochSource interface { + GetEpoch() uint64 +} + +// EpochContainer is an interface of the container of a NeoFS epoch number. +type EpochContainer interface { + EpochSource + SetEpoch(uint64) +} + +// TTLSource is an interface of the container of a numerical TTL value with read access. +type TTLSource interface { + GetTTL() uint32 +} + +// TTLContainer is an interface of the container of a numerical TTL value. +type TTLContainer interface { + TTLSource + SetTTL(uint32) +} + +// RequestMetaContainer is an interface of a fixed set of request meta value containers. +// Contains: +// - TTL value; +// - NeoFS epoch number; +// - Protocol version; +// - Raw toggle option. +type RequestMetaContainer interface { + TTLContainer + EpochContainer + VersionContainer + RawContainer +} + +// VerbSource is an interface of the container of a token verb value with read access. +type VerbSource interface { + GetVerb() Token_Info_Verb +} + +// VerbContainer is an interface of the container of a token verb value. +type VerbContainer interface { + VerbSource + SetVerb(Token_Info_Verb) +} + +// TokenIDSource is an interface of the container of a token ID value with read access. +type TokenIDSource interface { + GetID() TokenID +} + +// TokenIDContainer is an interface of the container of a token ID value. +type TokenIDContainer interface { + TokenIDSource + SetID(TokenID) +} + +// CreationEpochSource is an interface of the container of a creation epoch number with read access. +type CreationEpochSource interface { + CreationEpoch() uint64 +} + +// CreationEpochContainer is an interface of the container of a creation epoch number. +type CreationEpochContainer interface { + CreationEpochSource + SetCreationEpoch(uint64) +} + +// ExpirationEpochSource is an interface of the container of an expiration epoch number with read access. +type ExpirationEpochSource interface { + ExpirationEpoch() uint64 +} + +// ExpirationEpochContainer is an interface of the container of an expiration epoch number. +type ExpirationEpochContainer interface { + ExpirationEpochSource + SetExpirationEpoch(uint64) +} + +// SessionKeySource is an interface of the container of session key bytes with read access. +type SessionKeySource interface { + GetSessionKey() []byte +} + +// SessionKeyContainer is an interface of the container of public session key bytes. +type SessionKeyContainer interface { + SessionKeySource + SetSessionKey([]byte) +} + +// SignatureSource is an interface of the container of signature bytes with read access. +type SignatureSource interface { + GetSignature() []byte +} + +// SignatureContainer is an interface of the container of signature bytes. +type SignatureContainer interface { + SignatureSource + SetSignature([]byte) +} + +// SessionTokenSource is an interface of the container of a SessionToken with read access. +type SessionTokenSource interface { + GetSessionToken() SessionToken +} + +// SessionTokenInfo is an interface of a fixed set of token information value containers. +// Contains: +// - ID of the token; +// - ID of the token's owner; +// - verb of the session; +// - address of the session object; +// - creation epoch number of the token; +// - expiration epoch number of the token; +// - public session key bytes. +type SessionTokenInfo interface { + TokenIDContainer + OwnerIDContainer + VerbContainer + AddressContainer + CreationEpochContainer + ExpirationEpochContainer + SessionKeyContainer +} + +// SessionToken is an interface of token information and signature pair. +type SessionToken interface { + SessionTokenInfo + SignatureContainer +} From c38a8eddc898059e2ba948c64b710c6a4bb47c4c Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 14:03:11 +0300 Subject: [PATCH 04/19] service: use value container interfaces --- service/meta.go | 33 ++++----------------------------- service/ttl.go | 8 +------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/service/meta.go b/service/meta.go index 2675b79e..2714f086 100644 --- a/service/meta.go +++ b/service/meta.go @@ -9,35 +9,10 @@ type ( ResetMeta() RequestMetaHeader RestoreMeta(RequestMetaHeader) - // TTLHeader allows to get and set TTL value of request. - TTLHeader - - // EpochHeader gives possibility to get or set epoch in RPC Requests. - EpochHeader - - // VersionHeader allows get or set version of protocol request - VersionHeader - - // RawHeader allows to get and set raw option of request - RawHeader - } - - // EpochHeader interface gives possibility to get or set epoch in RPC Requests. - EpochHeader interface { - GetEpoch() uint64 - SetEpoch(v uint64) - } - - // VersionHeader allows get or set version of protocol request - VersionHeader interface { - GetVersion() uint32 - SetVersion(uint32) - } - - // RawHeader is an interface of the container of a boolean Raw value - RawHeader interface { - GetRaw() bool - SetRaw(bool) + TTLContainer + EpochContainer + VersionContainer + RawContainer } ) diff --git a/service/ttl.go b/service/ttl.go index c79cc85e..28a50921 100644 --- a/service/ttl.go +++ b/service/ttl.go @@ -6,12 +6,6 @@ import ( "google.golang.org/grpc/status" ) -// TTLHeader is an interface of the container of a numerical TTL value. -type TTLHeader interface { - GetTTL() uint32 - SetTTL(uint32) -} - // TTL constants. const ( // ZeroTTL is an upper bound of invalid TTL values. @@ -41,7 +35,7 @@ func IRNonForwarding(role NodeRole) TTLCondition { } // ProcessRequestTTL validates and updates requests with TTL. -func ProcessRequestTTL(req TTLHeader, cond ...TTLCondition) error { +func ProcessRequestTTL(req TTLContainer, cond ...TTLCondition) error { ttl := req.GetTTL() if ttl == ZeroTTL { From eb94cf754964c6f3fd23ea2e45dce82ca9d64f66 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 14:37:14 +0300 Subject: [PATCH 05/19] service: refactor MetaHeader interface --- object/service.go | 2 +- service/epoch.go | 11 +++++++++++ service/epoch_test.go | 21 +++++++++++++++++++++ service/meta.go | 39 ++++----------------------------------- service/meta_test.go | 38 ++++++++++++++++---------------------- service/raw.go | 6 ++++++ service/raw_test.go | 24 ++++++++++++++++++++++++ service/types.go | 12 ++++++++++++ service/verify.go | 8 ++++---- service/version.go | 11 +++++++++++ service/version_test.go | 21 +++++++++++++++++++++ 11 files changed, 131 insertions(+), 62 deletions(-) create mode 100644 service/epoch.go create mode 100644 service/epoch_test.go create mode 100644 service/raw.go create mode 100644 service/raw_test.go create mode 100644 service/version.go create mode 100644 service/version_test.go diff --git a/object/service.go b/object/service.go index 45a8d4b1..0e38d704 100644 --- a/object/service.go +++ b/object/service.go @@ -31,7 +31,7 @@ type ( // All object operations must have TTL, Epoch, Type, Container ID and // permission of usage previous network map. Request interface { - service.MetaHeader + service.SeizedRequestMetaContainer CID() CID Type() RequestType diff --git a/service/epoch.go b/service/epoch.go new file mode 100644 index 00000000..7a7a556e --- /dev/null +++ b/service/epoch.go @@ -0,0 +1,11 @@ +package service + +// SetEpoch is an Epoch field setter. +func (m *ResponseMetaHeader) SetEpoch(v uint64) { + m.Epoch = v +} + +// SetEpoch is an Epoch field setter. +func (m *RequestMetaHeader) SetEpoch(v uint64) { + m.Epoch = v +} diff --git a/service/epoch_test.go b/service/epoch_test.go new file mode 100644 index 00000000..47316c05 --- /dev/null +++ b/service/epoch_test.go @@ -0,0 +1,21 @@ +package service + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetSetEpoch(t *testing.T) { + v := uint64(5) + + items := []EpochContainer{ + new(ResponseMetaHeader), + new(RequestMetaHeader), + } + + for _, item := range items { + item.SetEpoch(v) + require.Equal(t, v, item.GetEpoch()) + } +} diff --git a/service/meta.go b/service/meta.go index 2714f086..3a945aca 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,44 +1,13 @@ package service -type ( - // MetaHeader contains meta information of request. - // It provides methods to get or set meta information meta header. - // Also contains methods to reset and restore meta header. - // Also contains methods to get or set request protocol version - MetaHeader interface { - ResetMeta() RequestMetaHeader - RestoreMeta(RequestMetaHeader) - - TTLContainer - EpochContainer - VersionContainer - RawContainer - } -) - -// SetVersion sets protocol version to ResponseMetaHeader. -func (m *ResponseMetaHeader) SetVersion(v uint32) { m.Version = v } - -// SetEpoch sets Epoch to ResponseMetaHeader. -func (m *ResponseMetaHeader) SetEpoch(v uint64) { m.Epoch = v } - -// SetVersion sets protocol version to RequestMetaHeader. -func (m *RequestMetaHeader) SetVersion(v uint32) { m.Version = v } - -// SetEpoch sets Epoch to RequestMetaHeader. -func (m *RequestMetaHeader) SetEpoch(v uint64) { m.Epoch = v } - -// SetRaw is a Raw field setter. -func (m *RequestMetaHeader) SetRaw(raw bool) { - m.Raw = raw -} - // ResetMeta returns current value and sets RequestMetaHeader to empty value. -func (m *RequestMetaHeader) ResetMeta() RequestMetaHeader { +func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { cp := *m m.Reset() return cp } // RestoreMeta sets current RequestMetaHeader to passed value. -func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { *m = v } +func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) { + *m = v +} diff --git a/service/meta_test.go b/service/meta_test.go index fb7fb171..a0b85ef9 100644 --- a/service/meta_test.go +++ b/service/meta_test.go @@ -6,26 +6,20 @@ import ( "github.com/stretchr/testify/require" ) -func TestRequestMetaHeader_SetEpoch(t *testing.T) { - m := new(ResponseMetaHeader) - epoch := uint64(3) - m.SetEpoch(epoch) - require.Equal(t, epoch, m.GetEpoch()) -} - -func TestRequestMetaHeader_SetVersion(t *testing.T) { - m := new(ResponseMetaHeader) - version := uint32(3) - m.SetVersion(version) - require.Equal(t, version, m.GetVersion()) -} - -func TestRequestMetaHeader_SetRaw(t *testing.T) { - m := new(RequestMetaHeader) - - m.SetRaw(true) - require.True(t, m.GetRaw()) - - m.SetRaw(false) - require.False(t, m.GetRaw()) +func TestCutRestoreMeta(t *testing.T) { + items := []func() SeizedMetaHeaderContainer{ + func() SeizedMetaHeaderContainer { + m := new(RequestMetaHeader) + m.SetEpoch(1) + return m + }, + } + + for _, item := range items { + v1 := item() + m1 := v1.CutMeta() + v1.RestoreMeta(m1) + + require.Equal(t, item(), v1) + } } diff --git a/service/raw.go b/service/raw.go new file mode 100644 index 00000000..0bb4b275 --- /dev/null +++ b/service/raw.go @@ -0,0 +1,6 @@ +package service + +// SetRaw is a Raw field setter. +func (m *RequestMetaHeader) SetRaw(raw bool) { + m.Raw = raw +} diff --git a/service/raw_test.go b/service/raw_test.go new file mode 100644 index 00000000..ad595edc --- /dev/null +++ b/service/raw_test.go @@ -0,0 +1,24 @@ +package service + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetSetRaw(t *testing.T) { + items := []RawContainer{ + new(RequestMetaHeader), + } + + for _, item := range items { + // init with false + item.SetRaw(false) + + item.SetRaw(true) + require.True(t, item.GetRaw()) + + item.SetRaw(false) + require.False(t, item.GetRaw()) + } +} diff --git a/service/types.go b/service/types.go index cad4b3c4..e7bdcd47 100644 --- a/service/types.go +++ b/service/types.go @@ -51,6 +51,12 @@ type TTLContainer interface { SetTTL(uint32) } +// SeizedMetaHeaderContainer is an interface of container of RequestMetaHeader that can be cut and restored. +type SeizedMetaHeaderContainer interface { + CutMeta() RequestMetaHeader + RestoreMeta(RequestMetaHeader) +} + // RequestMetaContainer is an interface of a fixed set of request meta value containers. // Contains: // - TTL value; @@ -64,6 +70,12 @@ type RequestMetaContainer interface { RawContainer } +// SeizedRequestMetaContainer is a RequestMetaContainer with seized meta. +type SeizedRequestMetaContainer interface { + RequestMetaContainer + SeizedMetaHeaderContainer +} + // VerbSource is an interface of the container of a token verb value with read access. type VerbSource interface { GetVerb() Token_Info_Verb diff --git a/service/verify.go b/service/verify.go index 7ac3cf30..3ed8402a 100644 --- a/service/verify.go +++ b/service/verify.go @@ -118,8 +118,8 @@ var bytesPool = sync.Pool{New: func() interface{} { // new signature to headers. If something went wrong, returns error. func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error { // ignore meta header - if meta, ok := msg.(MetaHeader); ok { - h := meta.ResetMeta() + if meta, ok := msg.(SeizedRequestMetaContainer); ok { + h := meta.CutMeta() defer func() { meta.RestoreMeta(h) @@ -157,8 +157,8 @@ func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error { // If something went wrong, returns error. func VerifyRequestHeader(msg VerifiableRequest) error { // ignore meta header - if meta, ok := msg.(MetaHeader); ok { - h := meta.ResetMeta() + if meta, ok := msg.(SeizedRequestMetaContainer); ok { + h := meta.CutMeta() defer func() { meta.RestoreMeta(h) diff --git a/service/version.go b/service/version.go new file mode 100644 index 00000000..6f4839cd --- /dev/null +++ b/service/version.go @@ -0,0 +1,11 @@ +package service + +// SetVersion is a Version field setter. +func (m *ResponseMetaHeader) SetVersion(v uint32) { + m.Version = v +} + +// SetVersion is a Version field setter. +func (m *RequestMetaHeader) SetVersion(v uint32) { + m.Version = v +} diff --git a/service/version_test.go b/service/version_test.go new file mode 100644 index 00000000..d102d305 --- /dev/null +++ b/service/version_test.go @@ -0,0 +1,21 @@ +package service + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetSetVersion(t *testing.T) { + v := uint32(7) + + items := []VersionContainer{ + new(ResponseMetaHeader), + new(RequestMetaHeader), + } + + for _, item := range items { + item.SetVersion(v) + require.Equal(t, v, item.GetVersion()) + } +} From 0ffb1bd61df9a8420fd71001af629a1e45d056c1 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 18:52:56 +0300 Subject: [PATCH 06/19] service: implement a function for signing data --- service/errors.go | 3 ++ service/sign.go | 58 ++++++++++++++++++++++ service/sign_test.go | 112 +++++++++++++++++++++++++++++++++++++++++++ service/types.go | 16 +++++++ 4 files changed, 189 insertions(+) create mode 100644 service/sign.go create mode 100644 service/sign_test.go diff --git a/service/errors.go b/service/errors.go index 4aefb4eb..1154a281 100644 --- a/service/errors.go +++ b/service/errors.go @@ -16,3 +16,6 @@ const ErrCannotFindOwner = internal.Error("cannot find owner public key") // ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey const ErrWrongOwner = internal.Error("wrong owner") + +// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource argument, but received nil. +const ErrNilSignedDataSource = internal.Error("signed data source is nil") diff --git a/service/sign.go b/service/sign.go new file mode 100644 index 00000000..f3714833 --- /dev/null +++ b/service/sign.go @@ -0,0 +1,58 @@ +package service + +import ( + "crypto/ecdsa" + + crypto "github.com/nspcc-dev/neofs-crypto" +) + +// Returns data from DataSignatureAccumulator for signature creation/verification. +// +// If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained +// using this interface for optimization. In this case, it is understood that reading into the slice D +// that the method DataForSignature returns does not change D. +func dataForSignature(src SignedDataSource) ([]byte, error) { + r, ok := src.(SignedDataReader) + if !ok { + return src.SignedData() + } + + buf := bytesPool.Get().([]byte) + defer func() { + bytesPool.Put(buf) + }() + + if size := r.SignedDataSize(); size <= cap(buf) { + buf = buf[:size] + } else { + buf = make([]byte, size) + } + + n, err := r.ReadSignedData(buf) + if err != nil { + return nil, err + } + + return buf[:n], nil + +} + +// DataSignature returns the signature of data obtained using the private key. +// +// If passed data container is nil, ErrNilSignedDataSource returns. +// If passed private key is nil, crypto.ErrEmptyPrivateKey returns. +// If the data container or the signature function returns an error, it is returned directly. +func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) { + if src == nil { + return nil, ErrNilSignedDataSource + } else if key == nil { + return nil, crypto.ErrEmptyPrivateKey + } + + data, err := dataForSignature(src) + if err != nil { + return nil, err + } + + return crypto.Sign(key, data) +} diff --git a/service/sign_test.go b/service/sign_test.go new file mode 100644 index 00000000..be5f4b7f --- /dev/null +++ b/service/sign_test.go @@ -0,0 +1,112 @@ +package service + +import ( + "crypto/rand" + "errors" + "io" + "testing" + + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +type testSignedDataSrc struct { + e error + d []byte +} + +type testSignedDataReader struct { + SignedDataSource + + e error + d []byte +} + +func testData(t *testing.T, sz int) []byte { + d := make([]byte, sz) + _, err := rand.Read(d) + require.NoError(t, err) + return d +} + +func (s testSignedDataReader) SignedDataSize() int { + return len(s.d) +} + +func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) { + if s.e != nil { + return 0, s.e + } + + var err error + if len(buf) < len(s.d) { + err = io.ErrUnexpectedEOF + } + return copy(buf, s.d), err +} + +func (s testSignedDataSrc) SignedData() ([]byte, error) { + return s.d, s.e +} + +func TestDataSignature(t *testing.T) { + var err error + + // nil data source + _, err = DataSignature(nil, nil) + require.EqualError(t, err, ErrNilSignedDataSource.Error()) + + // nil private key + _, err = DataSignature(new(testSignedDataSrc), nil) + require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error()) + + // create test private key + sk := test.DecodeKey(0) + + t.Run("common signed data source", func(t *testing.T) { + // create test data source + src := &testSignedDataSrc{ + d: testData(t, 10), + } + + // create custom error for data source + src.e = errors.New("test error for data source") + + _, err = DataSignature(src, sk) + require.EqualError(t, err, src.e.Error()) + + // reset error to nil + src.e = nil + + // calculate data signature + sig, err := DataSignature(src, sk) + require.NoError(t, err) + + // ascertain that the signature passes verification + require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig)) + }) + + t.Run("signed data reader", func(t *testing.T) { + // create test signed data reader + src := &testSignedDataReader{ + d: testData(t, 10), + } + + // create custom error for signed data reader + src.e = errors.New("test error for signed data reader") + + sig, err := DataSignature(src, sk) + require.EqualError(t, err, src.e.Error()) + + // reset error to nil + src.e = nil + + // calculate data signature + sig, err = DataSignature(src, sk) + require.NoError(t, err) + + // ascertain that the signature passes verification + require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig)) + }) +} diff --git a/service/types.go b/service/types.go index e7bdcd47..06ea4e27 100644 --- a/service/types.go +++ b/service/types.go @@ -171,3 +171,19 @@ type SessionToken interface { SessionTokenInfo SignatureContainer } + +// SignedDataSource is an interface of the container of a data for signing. +type SignedDataSource interface { + // Must return the required for signature byte slice. + // A non-nil error indicates that the data is not ready for signature. + SignedData() ([]byte, error) +} + +// SignedDataReader is an interface of signed data reader. +type SignedDataReader interface { + // Must return the minimum length of the slice for full reading. + SignedDataSize() int + + // Must behave like Read method of io.Reader and differ only in the reading of the signed data. + ReadSignedData([]byte) (int, error) +} From f3e6caf7e75cf378da1c279d72ae6cd46d9d1fc0 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 4 May 2020 19:33:18 +0300 Subject: [PATCH 07/19] service: implement a function for creating and storing a signature --- service/sign.go | 14 ++++++++++++++ service/sign_test.go | 35 +++++++++++++++++++++++++++++++++++ service/types.go | 10 ++++++++++ 3 files changed, 59 insertions(+) diff --git a/service/sign.go b/service/sign.go index f3714833..654c4a9c 100644 --- a/service/sign.go +++ b/service/sign.go @@ -56,3 +56,17 @@ func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) return crypto.Sign(key, data) } + +// AddSignatureWithKey calculates the data signature and adds it to accumulator with public key. +// +// Returns signing errors only. +func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error { + sign, err := DataSignature(v, key) + if err != nil { + return err + } + + v.AddSignKey(sign, &key.PublicKey) + + return nil +} diff --git a/service/sign_test.go b/service/sign_test.go index be5f4b7f..5ac7c6c4 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -1,6 +1,7 @@ package service import ( + "crypto/ecdsa" "crypto/rand" "errors" "io" @@ -23,6 +24,21 @@ type testSignedDataReader struct { d []byte } +type testKeySigAccum struct { + d []byte + f func([]byte, *ecdsa.PublicKey) +} + +func (s testKeySigAccum) SignedData() ([]byte, error) { + return s.d, nil +} + +func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) { + if s.f != nil { + s.f(sig, key) + } +} + func testData(t *testing.T, sz int) []byte { d := make([]byte, sz) _, err := rand.Read(d) @@ -110,3 +126,22 @@ func TestDataSignature(t *testing.T) { require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig)) }) } + +func TestAddSignatureWithKey(t *testing.T) { + // create test data + data := testData(t, 10) + + // create test private key + sk := test.DecodeKey(0) + + // create test signature accumulator + var s SignatureKeyAccumulator = &testKeySigAccum{ + d: data, + f: func(sig []byte, key *ecdsa.PublicKey) { + require.Equal(t, &sk.PublicKey, key) + require.NoError(t, crypto.Verify(key, data, sig)) + }, + } + + require.NoError(t, AddSignatureWithKey(s, sk)) +} diff --git a/service/types.go b/service/types.go index 06ea4e27..80a4a493 100644 --- a/service/types.go +++ b/service/types.go @@ -1,5 +1,9 @@ package service +import ( + "crypto/ecdsa" +) + // NodeRole to identify in Bootstrap service. type NodeRole int32 @@ -187,3 +191,9 @@ type SignedDataReader interface { // Must behave like Read method of io.Reader and differ only in the reading of the signed data. ReadSignedData([]byte) (int, error) } + +// SignatureKeyAccumulator is an interface of the accumulator of data signatures with keys. +type SignatureKeyAccumulator interface { + SignedDataSource + AddSignKey([]byte, *ecdsa.PublicKey) +} From 74144f207aa1eaf88f1e55286a7e4264c2090d09 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 5 May 2020 13:16:21 +0300 Subject: [PATCH 08/19] service: implement functions for verification of signatures --- service/errors.go | 8 ++- service/sign.go | 103 ++++++++++++++++++++++++++-- service/sign_test.go | 160 +++++++++++++++++++++++++++++++++++++------ service/types.go | 20 +++++- 4 files changed, 264 insertions(+), 27 deletions(-) diff --git a/service/errors.go b/service/errors.go index 1154a281..6b0ed24d 100644 --- a/service/errors.go +++ b/service/errors.go @@ -17,5 +17,11 @@ const ErrCannotFindOwner = internal.Error("cannot find owner public key") // ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey const ErrWrongOwner = internal.Error("wrong owner") -// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource argument, but received nil. +// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource, but received nil. const ErrNilSignedDataSource = internal.Error("signed data source is nil") + +// ErrNilSignatureKeySource is returned by functions that expect a non-nil SignatureKeySource, but received nil. +const ErrNilSignatureKeySource = internal.Error("empty key-signature source") + +// ErrEmptyDataWithSignature is returned by functions that expect a non-nil DataWithSignature, but received nil. +const ErrEmptyDataWithSignature = internal.Error("empty data with signature") diff --git a/service/sign.go b/service/sign.go index 654c4a9c..9a6eba99 100644 --- a/service/sign.go +++ b/service/sign.go @@ -6,12 +6,39 @@ import ( crypto "github.com/nspcc-dev/neofs-crypto" ) +type keySign struct { + key *ecdsa.PublicKey + sign []byte +} + +// GetSignature is a sign field getter. +func (s keySign) GetSignature() []byte { + return s.sign +} + +// GetPublicKey is a key field getter, +func (s keySign) GetPublicKey() *ecdsa.PublicKey { + return s.key +} + +// Unites passed key with signature and returns SignKeyPair interface. +func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair { + return &keySign{ + key: key, + sign: sign, + } +} + // Returns data from DataSignatureAccumulator for signature creation/verification. // // If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained // using this interface for optimization. In this case, it is understood that reading into the slice D // that the method DataForSignature returns does not change D. func dataForSignature(src SignedDataSource) ([]byte, error) { + if src == nil { + return nil, ErrNilSignedDataSource + } + r, ok := src.(SignedDataReader) if !ok { return src.SignedData() @@ -42,10 +69,8 @@ func dataForSignature(src SignedDataSource) ([]byte, error) { // If passed data container is nil, ErrNilSignedDataSource returns. // If passed private key is nil, crypto.ErrEmptyPrivateKey returns. // If the data container or the signature function returns an error, it is returned directly. -func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) { - if src == nil { - return nil, ErrNilSignedDataSource - } else if key == nil { +func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) { + if key == nil { return nil, crypto.ErrEmptyPrivateKey } @@ -61,7 +86,7 @@ func DataSignature(src SignedDataSource, key *ecdsa.PrivateKey) ([]byte, error) // // Returns signing errors only. func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error { - sign, err := DataSignature(v, key) + sign, err := DataSignature(key, v) if err != nil { return err } @@ -70,3 +95,71 @@ func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error return nil } + +// Checks passed key-signature pairs for data from the passed container. +// +// If passed key-signatures pair set is empty, nil returns immediately. +func verifySignatures(src SignedDataSource, items ...SignKeyPair) error { + if len(items) <= 0 { + return nil + } + + data, err := dataForSignature(src) + if err != nil { + return err + } + + for _, signKey := range items { + if err := crypto.Verify( + signKey.GetPublicKey(), + data, + signKey.GetSignature(), + ); err != nil { + return err + } + } + + return nil +} + +// VerifySignatures checks passed key-signature pairs for data from the passed container. +// +// If passed data source is nil, ErrNilSignedDataSource returns. +// If check data is not ready, corresponding error returns. +// If at least one of the pairs is invalid, an error returns. +func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error { + return verifySignatures(src, items...) +} + +// VerifyAccumulatedSignatures checks if accumulated key-signature pairs are valid. +// +// Behaves like VerifySignatures. +// If passed key-signature source is empty, ErrNilSignatureKeySource returns. +func VerifyAccumulatedSignatures(src SignatureKeySource) error { + if src == nil { + return ErrNilSignatureKeySource + } + + return verifySignatures(src, src.GetSignKeyPairs()...) +} + +// VerifySignatureWithKey checks data signature from the passed container with passed key. +// +// If passed data with signature is nil, ErrEmptyDataWithSignature returns. +// If passed key is nil, crypto.ErrEmptyPublicKey returns. +// A non-nil error returns if and only if the signature does not pass verification. +func VerifySignatureWithKey(src DataWithSignature, key *ecdsa.PublicKey) error { + if src == nil { + return ErrEmptyDataWithSignature + } else if key == nil { + return crypto.ErrEmptyPublicKey + } + + return verifySignatures( + src, + newSignatureKeyPair( + key, + src.GetSignature(), + ), + ) +} diff --git a/service/sign_test.go b/service/sign_test.go index 5ac7c6c4..8fba9689 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -25,18 +25,28 @@ type testSignedDataReader struct { } type testKeySigAccum struct { - d []byte - f func([]byte, *ecdsa.PublicKey) + data []byte + sig []byte + key *ecdsa.PublicKey +} + +func (s testKeySigAccum) GetSignature() []byte { + return s.sig +} + +func (s testKeySigAccum) GetSignKeyPairs() []SignKeyPair { + return []SignKeyPair{ + newSignatureKeyPair(s.key, s.sig), + } } func (s testKeySigAccum) SignedData() ([]byte, error) { - return s.d, nil + return s.data, nil } func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) { - if s.f != nil { - s.f(sig, key) - } + s.key = key + s.sig = sig } func testData(t *testing.T, sz int) []byte { @@ -69,17 +79,17 @@ func (s testSignedDataSrc) SignedData() ([]byte, error) { func TestDataSignature(t *testing.T) { var err error - // nil data source - _, err = DataSignature(nil, nil) - require.EqualError(t, err, ErrNilSignedDataSource.Error()) - // nil private key - _, err = DataSignature(new(testSignedDataSrc), nil) + _, err = DataSignature(nil, nil) require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error()) // create test private key sk := test.DecodeKey(0) + // nil private key + _, err = DataSignature(sk, nil) + require.EqualError(t, err, ErrNilSignedDataSource.Error()) + t.Run("common signed data source", func(t *testing.T) { // create test data source src := &testSignedDataSrc{ @@ -89,14 +99,14 @@ func TestDataSignature(t *testing.T) { // create custom error for data source src.e = errors.New("test error for data source") - _, err = DataSignature(src, sk) + _, err = DataSignature(sk, src) require.EqualError(t, err, src.e.Error()) // reset error to nil src.e = nil // calculate data signature - sig, err := DataSignature(src, sk) + sig, err := DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification @@ -112,14 +122,14 @@ func TestDataSignature(t *testing.T) { // create custom error for signed data reader src.e = errors.New("test error for signed data reader") - sig, err := DataSignature(src, sk) + sig, err := DataSignature(sk, src) require.EqualError(t, err, src.e.Error()) // reset error to nil src.e = nil // calculate data signature - sig, err = DataSignature(src, sk) + sig, err = DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification @@ -136,12 +146,122 @@ func TestAddSignatureWithKey(t *testing.T) { // create test signature accumulator var s SignatureKeyAccumulator = &testKeySigAccum{ - d: data, - f: func(sig []byte, key *ecdsa.PublicKey) { - require.Equal(t, &sk.PublicKey, key) - require.NoError(t, crypto.Verify(key, data, sig)) - }, + data: data, } require.NoError(t, AddSignatureWithKey(s, sk)) } + +func TestVerifySignatures(t *testing.T) { + // empty signatures + require.NoError(t, VerifySignatures(nil)) + + // create test signature source + src := &testSignedDataSrc{ + d: testData(t, 10), + } + + // create private key for test + sk := test.DecodeKey(0) + + // calculate a signature of the data + sig, err := crypto.Sign(sk, src.d) + require.NoError(t, err) + + // ascertain that verification is passed + require.NoError(t, + VerifySignatures( + src, + newSignatureKeyPair(&sk.PublicKey, sig), + ), + ) + + // break the signature + sig[0]++ + + require.Error(t, + VerifySignatures( + src, + newSignatureKeyPair(&sk.PublicKey, sig), + ), + ) + + // restore the signature + sig[0]-- + + // empty data source + require.EqualError(t, + VerifySignatures(nil, nil), + ErrNilSignedDataSource.Error(), + ) + +} + +func TestVerifyAccumulatedSignatures(t *testing.T) { + // nil signature source + require.EqualError(t, + VerifyAccumulatedSignatures(nil), + ErrNilSignatureKeySource.Error(), + ) + + // create test private key + sk := test.DecodeKey(0) + + // create signature source + src := &testKeySigAccum{ + data: testData(t, 10), + key: &sk.PublicKey, + } + + var err error + + // calculate a signature + src.sig, err = crypto.Sign(sk, src.data) + require.NoError(t, err) + + // ascertain that verification is passed + require.NoError(t, VerifyAccumulatedSignatures(src)) + + // break the signature + src.sig[0]++ + + // ascertain that verification is failed + require.Error(t, VerifyAccumulatedSignatures(src)) +} + +func TestVerifySignatureWithKey(t *testing.T) { + // nil signature source + require.EqualError(t, + VerifySignatureWithKey(nil, nil), + ErrEmptyDataWithSignature.Error(), + ) + + // create test signature source + src := &testKeySigAccum{ + data: testData(t, 10), + } + + // nil public key + require.EqualError(t, + VerifySignatureWithKey(src, nil), + crypto.ErrEmptyPublicKey.Error(), + ) + + // create test private key + sk := test.DecodeKey(0) + + var err error + + // calculate a signature + src.sig, err = crypto.Sign(sk, src.data) + require.NoError(t, err) + + // ascertain that verification is passed + require.NoError(t, VerifySignatureWithKey(src, &sk.PublicKey)) + + // break the signature + src.sig[0]++ + + // ascertain that verification is failed + require.Error(t, VerifySignatureWithKey(src, &sk.PublicKey)) +} diff --git a/service/types.go b/service/types.go index 80a4a493..3bf8c3a2 100644 --- a/service/types.go +++ b/service/types.go @@ -192,8 +192,26 @@ type SignedDataReader interface { ReadSignedData([]byte) (int, error) } -// SignatureKeyAccumulator is an interface of the accumulator of data signatures with keys. +// SignatureKeyAccumulator is an interface of the container of a data and signatures. type SignatureKeyAccumulator interface { SignedDataSource AddSignKey([]byte, *ecdsa.PublicKey) } + +// SignKeyPair is an interface of key-signature pair with read access. +type SignKeyPair interface { + SignatureSource + GetPublicKey() *ecdsa.PublicKey +} + +// SignatureKeyAccumulator is an interface of the container of a data and signatures with read access. +type SignatureKeySource interface { + SignedDataSource + GetSignKeyPairs() []SignKeyPair +} + +// DataWithSignature is an interface of data-signature pair with read access. +type DataWithSignature interface { + SignedDataSource + SignatureSource +} From fc2c78ae896515ba6efe4a5a01b99fc5fa3bdffa Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 5 May 2020 14:49:35 +0300 Subject: [PATCH 09/19] service: use new function for token signing and verification --- service/token.go | 147 ++++++++++++++++++++---------------------- service/token_test.go | 33 ++-------- 2 files changed, 76 insertions(+), 104 deletions(-) diff --git a/service/token.go b/service/token.go index 2aa159b4..5786a40a 100644 --- a/service/token.go +++ b/service/token.go @@ -3,10 +3,22 @@ package service import ( "crypto/ecdsa" "encoding/binary" + "io" - crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-api-go/refs" ) +const verbSize = 4 + +const fixedTokenDataSize = 0 + + refs.UUIDSize + + refs.OwnerIDSize + + verbSize + + refs.UUIDSize + + refs.CIDSize + + 8 + + 8 + var tokenEndianness = binary.BigEndian // GetID is an ID field getter. @@ -74,88 +86,71 @@ func (m *Token) SetSignature(sig []byte) { m.Signature = sig } -// Returns byte slice that is used for creation/verification of the token signature. -func verificationTokenData(token SessionToken) []byte { - var sz int - - id := token.GetID() - sz += id.Size() - - ownerID := token.GetOwnerID() - sz += ownerID.Size() - - verb := uint32(token.GetVerb()) - sz += 4 - - addr := token.GetAddress() - sz += addr.CID.Size() + addr.ObjectID.Size() - - cEpoch := token.CreationEpoch() - sz += 8 - - fEpoch := token.ExpirationEpoch() - sz += 8 - - key := token.GetSessionKey() - sz += len(key) - - data := make([]byte, sz) - - var off int - - tokenEndianness.PutUint32(data, verb) - off += 4 - - tokenEndianness.PutUint64(data[off:], cEpoch) - off += 8 - - tokenEndianness.PutUint64(data[off:], fEpoch) - off += 8 - - off += copy(data[off:], id.Bytes()) - off += copy(data[off:], ownerID.Bytes()) - off += copy(data[off:], addr.CID.Bytes()) - off += copy(data[off:], addr.ObjectID.Bytes()) - off += copy(data[off:], key) +// Size returns the size of a binary representation of the verb. +func (x Token_Info_Verb) Size() int { + return verbSize +} +// Bytes returns a binary representation of the verb. +func (x Token_Info_Verb) Bytes() []byte { + data := make([]byte, verbSize) + tokenEndianness.PutUint32(data, uint32(x)) return data } -// SignToken calculates and stores the signature of token information. -// -// If passed token is nil, ErrNilToken returns. -// If passed private key is nil, crypto.ErrEmptyPrivateKey returns. -func SignToken(token SessionToken, key *ecdsa.PrivateKey) error { - if token == nil { - return ErrNilToken - } else if key == nil { - return crypto.ErrEmptyPrivateKey - } - - sig, err := crypto.Sign(key, verificationTokenData(token)) - if err != nil { - return err - } - - token.SetSignature(sig) - - return nil +// AddSignKey calls a Signature field setter with passed signature. +func (m *Token) AddSignKey(sig []byte, _ *ecdsa.PublicKey) { + m.SetSignature(sig) } -// VerifyTokenSignature checks if token was signed correctly. +// 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 +} + +// ReadSignedData copies a binary representation of the token information to passed buffer. // -// If passed token is nil, ErrNilToken returns. -// If passed public key is nil, crypto.ErrEmptyPublicKey returns. -func VerifyTokenSignature(token SessionToken, key *ecdsa.PublicKey) error { - if token == nil { - return ErrNilToken - } else if key == nil { - return crypto.ErrEmptyPublicKey +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (m *Token_Info) ReadSignedData(p []byte) (int, error) { + sz := m.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF } - return crypto.Verify( - key, - verificationTokenData(token), - token.GetSignature(), - ) + copyTokenSignedData(p, m) + + return sz, nil +} + +// SignedDataSize returns the length of signed token information slice. +func (m Token_Info) SignedDataSize() int { + return fixedTokenDataSize + len(m.GetSessionKey()) +} + +// Fills passed buffer with signing token information bytes. +// Does not check buffer length, it is understood that enough space is allocated in it. +func copyTokenSignedData(buf []byte, token SessionTokenInfo) { + var off int + + off += copy(buf[off:], token.GetID().Bytes()) + + off += copy(buf[off:], token.GetOwnerID().Bytes()) + + off += copy(buf[off:], token.GetVerb().Bytes()) + + addr := token.GetAddress() + off += copy(buf[off:], addr.CID.Bytes()) + off += copy(buf[off:], addr.ObjectID.Bytes()) + + tokenEndianness.PutUint64(buf[off:], token.CreationEpoch()) + off += 8 + + tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch()) + off += 8 + + copy(buf[off:], token.GetSessionKey()) } diff --git a/service/token_test.go b/service/token_test.go index 1e02f468..968364c5 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/refs" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-crypto/test" "github.com/stretchr/testify/require" ) @@ -90,29 +89,7 @@ func TestTokenGettersSetters(t *testing.T) { } func TestSignToken(t *testing.T) { - // nil token - require.EqualError(t, - SignToken(nil, nil), - ErrNilToken.Error(), - ) - - require.EqualError(t, - VerifyTokenSignature(nil, nil), - ErrNilToken.Error(), - ) - - var token SessionToken = new(Token) - - // nil key - require.EqualError(t, - SignToken(token, nil), - crypto.ErrEmptyPrivateKey.Error(), - ) - - require.EqualError(t, - VerifyTokenSignature(token, nil), - crypto.ErrEmptyPublicKey.Error(), - ) + token := new(Token) // create private key for signing sk := test.DecodeKey(0) @@ -150,8 +127,8 @@ func TestSignToken(t *testing.T) { token.SetSessionKey(sessionKey) // sign and verify token - require.NoError(t, SignToken(token, sk)) - require.NoError(t, VerifyTokenSignature(token, pk)) + require.NoError(t, AddSignatureWithKey(token, sk)) + require.NoError(t, VerifySignatureWithKey(token, pk)) items := []struct { corrupt func() @@ -235,8 +212,8 @@ func TestSignToken(t *testing.T) { for _, v := range items { v.corrupt() - require.Error(t, VerifyTokenSignature(token, pk)) + require.Error(t, VerifySignatureWithKey(token, pk)) v.restore() - require.NoError(t, VerifyTokenSignature(token, pk)) + require.NoError(t, VerifySignatureWithKey(token, pk)) } } From 52d3c827763d28b3d8324d1e408b3b5f314c6ec0 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 11:44:55 +0300 Subject: [PATCH 10/19] service: implement sign/verify function for data with session token --- service/errors.go | 30 ++++++-- service/sign.go | 71 ++++++++++++++++-- service/sign_test.go | 171 ++++++++++++++++++++++++++++-------------- service/token.go | 83 +++++++++++++++++++- service/token_test.go | 8 +- service/types.go | 47 +++++++++--- 6 files changed, 326 insertions(+), 84 deletions(-) diff --git a/service/errors.go b/service/errors.go index 6b0ed24d..6241ad2c 100644 --- a/service/errors.go +++ b/service/errors.go @@ -2,10 +2,12 @@ package service import "github.com/nspcc-dev/neofs-api-go/internal" -// ErrNilToken is returned by functions that expect a non-nil token argument, but received nil. +// ErrNilToken is returned by functions that expect +// a non-nil token argument, but received nil. const ErrNilToken = internal.Error("token is nil") -// ErrInvalidTTL means that the TTL value does not satisfy a specific criterion. +// ErrInvalidTTL means that the TTL value does not +// satisfy a specific criterion. const ErrInvalidTTL = internal.Error("invalid TTL value") // ErrInvalidPublicKeyBytes means that the public key could not be unmarshaled. @@ -14,14 +16,30 @@ const ErrInvalidPublicKeyBytes = internal.Error("cannot load public key") // ErrCannotFindOwner is raised when signatures empty in GetOwner. const ErrCannotFindOwner = internal.Error("cannot find owner public key") -// ErrWrongOwner is raised when passed OwnerID not equal to present PublicKey +// ErrWrongOwner is raised when passed OwnerID +// not equal to present PublicKey const ErrWrongOwner = internal.Error("wrong owner") -// ErrNilSignedDataSource returned by functions that expect a non-nil SignedDataSource, but received nil. +// ErrNilSignedDataSource returned by functions that expect a non-nil +// SignedDataSource, but received nil. const ErrNilSignedDataSource = internal.Error("signed data source is nil") -// ErrNilSignatureKeySource is returned by functions that expect a non-nil SignatureKeySource, but received nil. +// ErrNilSignatureKeySource is returned by functions that expect a non-nil +// SignatureKeySource, but received nil. const ErrNilSignatureKeySource = internal.Error("empty key-signature source") -// ErrEmptyDataWithSignature is returned by functions that expect a non-nil DataWithSignature, but received nil. +// ErrEmptyDataWithSignature is returned by functions that expect +// a non-nil DataWithSignature, but received nil. const ErrEmptyDataWithSignature = internal.Error("empty data with signature") + +// ErrNegativeLength is returned by functions that received +// 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") + +// 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") diff --git a/service/sign.go b/service/sign.go index 9a6eba99..f5cdc0b9 100644 --- a/service/sign.go +++ b/service/sign.go @@ -34,6 +34,8 @@ func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair { // If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained // using this interface for optimization. In this case, it is understood that reading into the slice D // that the method DataForSignature returns does not change D. +// +// If returned length of data is negative, ErrNegativeLength returns. func dataForSignature(src SignedDataSource) ([]byte, error) { if src == nil { return nil, ErrNilSignedDataSource @@ -45,11 +47,10 @@ func dataForSignature(src SignedDataSource) ([]byte, error) { } buf := bytesPool.Get().([]byte) - defer func() { - bytesPool.Put(buf) - }() - if size := r.SignedDataSize(); size <= cap(buf) { + if size := r.SignedDataSize(); size < 0 { + return nil, ErrNegativeLength + } else if size <= cap(buf) { buf = buf[:size] } else { buf = make([]byte, size) @@ -78,14 +79,17 @@ func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) if err != nil { return nil, err } + defer bytesPool.Put(data) return crypto.Sign(key, data) } // AddSignatureWithKey calculates the data signature and adds it to accumulator with public key. // +// Any change of data provoke signature breakdown. +// // Returns signing errors only. -func AddSignatureWithKey(v SignatureKeyAccumulator, key *ecdsa.PrivateKey) error { +func AddSignatureWithKey(key *ecdsa.PrivateKey, v DataWithSignKeyAccumulator) error { sign, err := DataSignature(key, v) if err != nil { return err @@ -108,6 +112,7 @@ func verifySignatures(src SignedDataSource, items ...SignKeyPair) error { if err != nil { return err } + defer bytesPool.Put(data) for _, signKey := range items { if err := crypto.Verify( @@ -135,7 +140,7 @@ func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error { // // Behaves like VerifySignatures. // If passed key-signature source is empty, ErrNilSignatureKeySource returns. -func VerifyAccumulatedSignatures(src SignatureKeySource) error { +func VerifyAccumulatedSignatures(src DataWithSignKeySource) error { if src == nil { return ErrNilSignatureKeySource } @@ -148,7 +153,7 @@ func VerifyAccumulatedSignatures(src SignatureKeySource) error { // If passed data with signature is nil, ErrEmptyDataWithSignature returns. // If passed key is nil, crypto.ErrEmptyPublicKey returns. // A non-nil error returns if and only if the signature does not pass verification. -func VerifySignatureWithKey(src DataWithSignature, key *ecdsa.PublicKey) error { +func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error { if src == nil { return ErrEmptyDataWithSignature } else if key == nil { @@ -163,3 +168,55 @@ func VerifySignatureWithKey(src DataWithSignature, key *ecdsa.PublicKey) error { ), ) } + +// SignDataWithSessionToken calculates data with token signature and adds it to accumulator. +// +// Any change of data or session token info 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 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 AddSignatureWithKey(key, &signAccumWithToken{ + SignedDataSource: src, + SignKeyPairAccumulator: src, + + token: src.GetSessionToken(), + }) +} + +// VerifyAccumulatedSignaturesWithToken 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 src == nil { + return ErrNilSignatureKeySourceWithToken + } else if r, ok := src.(SignedDataReader); ok { + return VerifyAccumulatedSignatures(&signDataReaderWithToken{ + SignedDataSource: src, + SignKeyPairSource: src, + + rdr: r, + token: src.GetSessionToken(), + }) + } + + return VerifyAccumulatedSignatures(&signAccumWithToken{ + SignedDataSource: src, + SignKeyPairSource: src, + + token: src.GetSessionToken(), + }) +} diff --git a/service/sign_test.go b/service/sign_test.go index 8fba9689..5cb7c409 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -13,38 +13,32 @@ import ( ) type testSignedDataSrc struct { - e error - d []byte + err error + data []byte + sig []byte + key *ecdsa.PublicKey + token SessionToken } type testSignedDataReader struct { - SignedDataSource - - e error - d []byte + *testSignedDataSrc } -type testKeySigAccum struct { - data []byte - sig []byte - key *ecdsa.PublicKey -} - -func (s testKeySigAccum) GetSignature() []byte { +func (s testSignedDataSrc) GetSignature() []byte { return s.sig } -func (s testKeySigAccum) GetSignKeyPairs() []SignKeyPair { +func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair { return []SignKeyPair{ newSignatureKeyPair(s.key, s.sig), } } -func (s testKeySigAccum) SignedData() ([]byte, error) { - return s.data, nil +func (s testSignedDataSrc) SignedData() ([]byte, error) { + return s.data, s.err } -func (s testKeySigAccum) AddSignKey(sig []byte, key *ecdsa.PublicKey) { +func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) { s.key = key s.sig = sig } @@ -56,24 +50,24 @@ func testData(t *testing.T, sz int) []byte { return d } +func (s testSignedDataSrc) GetSessionToken() SessionToken { + return s.token +} + func (s testSignedDataReader) SignedDataSize() int { - return len(s.d) + return len(s.data) } func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) { - if s.e != nil { - return 0, s.e + if s.err != nil { + return 0, s.err } var err error - if len(buf) < len(s.d) { + if len(buf) < len(s.data) { err = io.ErrUnexpectedEOF } - return copy(buf, s.d), err -} - -func (s testSignedDataSrc) SignedData() ([]byte, error) { - return s.d, s.e + return copy(buf, s.data), err } func TestDataSignature(t *testing.T) { @@ -93,63 +87,59 @@ func TestDataSignature(t *testing.T) { t.Run("common signed data source", func(t *testing.T) { // create test data source src := &testSignedDataSrc{ - d: testData(t, 10), + data: testData(t, 10), } // create custom error for data source - src.e = errors.New("test error for data source") + src.err = errors.New("test error for data source") _, err = DataSignature(sk, src) - require.EqualError(t, err, src.e.Error()) + require.EqualError(t, err, src.err.Error()) // reset error to nil - src.e = nil + src.err = nil // calculate data signature sig, err := DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification - require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig)) + require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig)) }) t.Run("signed data reader", func(t *testing.T) { // create test signed data reader - src := &testSignedDataReader{ - d: testData(t, 10), + src := &testSignedDataSrc{ + data: testData(t, 10), } // create custom error for signed data reader - src.e = errors.New("test error for signed data reader") + src.err = errors.New("test error for signed data reader") sig, err := DataSignature(sk, src) - require.EqualError(t, err, src.e.Error()) + require.EqualError(t, err, src.err.Error()) // reset error to nil - src.e = nil + src.err = nil // calculate data signature sig, err = DataSignature(sk, src) require.NoError(t, err) // ascertain that the signature passes verification - require.NoError(t, crypto.Verify(&sk.PublicKey, src.d, sig)) + require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig)) }) } func TestAddSignatureWithKey(t *testing.T) { - // create test data - data := testData(t, 10) - - // create test private key - sk := test.DecodeKey(0) - - // create test signature accumulator - var s SignatureKeyAccumulator = &testKeySigAccum{ - data: data, - } - - require.NoError(t, AddSignatureWithKey(s, sk)) + require.NoError(t, + AddSignatureWithKey( + test.DecodeKey(0), + &testSignedDataSrc{ + data: testData(t, 10), + }, + ), + ) } func TestVerifySignatures(t *testing.T) { @@ -158,14 +148,14 @@ func TestVerifySignatures(t *testing.T) { // create test signature source src := &testSignedDataSrc{ - d: testData(t, 10), + data: testData(t, 10), } // create private key for test sk := test.DecodeKey(0) // calculate a signature of the data - sig, err := crypto.Sign(sk, src.d) + sig, err := crypto.Sign(sk, src.data) require.NoError(t, err) // ascertain that verification is passed @@ -208,7 +198,7 @@ func TestVerifyAccumulatedSignatures(t *testing.T) { sk := test.DecodeKey(0) // create signature source - src := &testKeySigAccum{ + src := &testSignedDataSrc{ data: testData(t, 10), key: &sk.PublicKey, } @@ -237,13 +227,13 @@ func TestVerifySignatureWithKey(t *testing.T) { ) // create test signature source - src := &testKeySigAccum{ + src := &testSignedDataSrc{ data: testData(t, 10), } // nil public key require.EqualError(t, - VerifySignatureWithKey(src, nil), + VerifySignatureWithKey(nil, src), crypto.ErrEmptyPublicKey.Error(), ) @@ -257,11 +247,80 @@ func TestVerifySignatureWithKey(t *testing.T) { require.NoError(t, err) // ascertain that verification is passed - require.NoError(t, VerifySignatureWithKey(src, &sk.PublicKey)) + require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src)) // break the signature src.sig[0]++ // ascertain that verification is failed - require.Error(t, VerifySignatureWithKey(src, &sk.PublicKey)) + require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src)) +} + +func TestSignVerifyDataWithSessionToken(t *testing.T) { + // sign with empty DataWithTokenSignAccumulator + require.EqualError(t, + SignDataWithSessionToken(nil, nil), + ErrNilDataWithTokenSignAccumulator.Error(), + ) + + // verify with empty DataWithTokenSignSource + require.EqualError(t, + VerifyAccumulatedSignaturesWithToken(nil), + ErrNilSignatureKeySourceWithToken.Error(), + ) + + // create test session token + var ( + token = new(Token) + initVerb = Token_Info_Verb(1) + ) + + token.SetVerb(initVerb) + + // create test data with token + src := &testSignedDataSrc{ + data: testData(t, 10), + token: token, + } + + // create test private key + sk := test.DecodeKey(0) + + // sign with private key + require.NoError(t, SignDataWithSessionToken(sk, src)) + + // ascertain that verification is passed + require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) + + // break the data + src.data[0]++ + + // ascertain that verification is failed + require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) + + // restore the data + src.data[0]-- + + // break the token + token.SetVerb(initVerb + 1) + + // ascertain that verification is failed + require.Error(t, VerifyAccumulatedSignaturesWithToken(src)) + + // restore the token + token.SetVerb(initVerb) + + // ascertain that verification is passed + require.NoError(t, VerifyAccumulatedSignaturesWithToken(src)) + + // wrap to data reader + rdr := &testSignedDataReader{ + testSignedDataSrc: src, + } + + // sign with private key + require.NoError(t, SignDataWithSessionToken(sk, rdr)) + + // ascertain that verification is passed + require.NoError(t, VerifyAccumulatedSignaturesWithToken(rdr)) } diff --git a/service/token.go b/service/token.go index 5786a40a..f431427b 100644 --- a/service/token.go +++ b/service/token.go @@ -8,6 +8,24 @@ import ( "github.com/nspcc-dev/neofs-api-go/refs" ) +type signAccumWithToken struct { + SignedDataSource + SignKeyPairAccumulator + SignKeyPairSource + + token SessionToken +} + +type signDataReaderWithToken struct { + SignedDataSource + SignKeyPairAccumulator + SignKeyPairSource + + rdr SignedDataReader + + token SessionToken +} + const verbSize = 4 const fixedTokenDataSize = 0 + @@ -127,13 +145,26 @@ func (m *Token_Info) ReadSignedData(p []byte) (int, error) { } // SignedDataSize returns the length of signed token information slice. -func (m Token_Info) SignedDataSize() int { - return fixedTokenDataSize + len(m.GetSessionKey()) +func (m *Token_Info) SignedDataSize() int { + return tokenInfoSize(m) +} + +func tokenInfoSize(v SessionKeySource) int { + if v == nil { + return 0 + } + return fixedTokenDataSize + len(v.GetSessionKey()) } // 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 SessionTokenInfo, buffer remains unchanged. func copyTokenSignedData(buf []byte, token SessionTokenInfo) { + if token == nil { + return + } + var off int off += copy(buf[off:], token.GetID().Bytes()) @@ -154,3 +185,51 @@ func copyTokenSignedData(buf []byte, token SessionTokenInfo) { copy(buf[off:], token.GetSessionKey()) } + +// SignedData concatenates signed data with session token information. Returns concatenation result. +// +// Token bytes are added if and only if token is not nil. +func (s signAccumWithToken) SignedData() ([]byte, error) { + data, err := s.SignedDataSource.SignedData() + if err != nil { + return nil, err + } + + tokenData := make([]byte, tokenInfoSize(s.token)) + + copyTokenSignedData(tokenData, s.token) + + return append(data, tokenData...), nil +} + +func (s signDataReaderWithToken) SignedDataSize() int { + sz := s.rdr.SignedDataSize() + if sz < 0 { + return -1 + } + + sz += tokenInfoSize(s.token) + + return sz +} + +func (s signDataReaderWithToken) ReadSignedData(p []byte) (int, error) { + dataSize := s.rdr.SignedDataSize() + if dataSize < 0 { + return 0, ErrNegativeLength + } + + sumSize := dataSize + tokenInfoSize(s.token) + + if len(p) < sumSize { + return 0, io.ErrUnexpectedEOF + } + + if n, err := s.rdr.ReadSignedData(p); err != nil { + return n, err + } + + copyTokenSignedData(p[dataSize:], s.token) + + return sumSize, nil +} diff --git a/service/token_test.go b/service/token_test.go index 968364c5..ce3d2c86 100644 --- a/service/token_test.go +++ b/service/token_test.go @@ -127,8 +127,8 @@ func TestSignToken(t *testing.T) { token.SetSessionKey(sessionKey) // sign and verify token - require.NoError(t, AddSignatureWithKey(token, sk)) - require.NoError(t, VerifySignatureWithKey(token, pk)) + require.NoError(t, AddSignatureWithKey(sk, token)) + require.NoError(t, VerifySignatureWithKey(pk, token)) items := []struct { corrupt func() @@ -212,8 +212,8 @@ func TestSignToken(t *testing.T) { for _, v := range items { v.corrupt() - require.Error(t, VerifySignatureWithKey(token, pk)) + require.Error(t, VerifySignatureWithKey(pk, token)) v.restore() - require.NoError(t, VerifySignatureWithKey(token, pk)) + require.NoError(t, VerifySignatureWithKey(pk, token)) } } diff --git a/service/types.go b/service/types.go index 3bf8c3a2..020cba0d 100644 --- a/service/types.go +++ b/service/types.go @@ -186,32 +186,61 @@ type SignedDataSource interface { // SignedDataReader is an interface of signed data reader. type SignedDataReader interface { // Must return the minimum length of the slice for full reading. + // Must return a negative value if the length cannot be calculated. SignedDataSize() int // Must behave like Read method of io.Reader and differ only in the reading of the signed data. ReadSignedData([]byte) (int, error) } -// SignatureKeyAccumulator is an interface of the container of a data and signatures. -type SignatureKeyAccumulator interface { - SignedDataSource +// SignKeyPairAccumulator is an interface of a set of key-signature pairs with append access. +type SignKeyPairAccumulator interface { AddSignKey([]byte, *ecdsa.PublicKey) } +// SignKeyPairSource is an interface of a set of key-signature pairs with read access. +type SignKeyPairSource interface { + GetSignKeyPairs() []SignKeyPair +} + // SignKeyPair is an interface of key-signature pair with read access. type SignKeyPair interface { SignatureSource GetPublicKey() *ecdsa.PublicKey } -// SignatureKeyAccumulator is an interface of the container of a data and signatures with read access. -type SignatureKeySource interface { - SignedDataSource - GetSignKeyPairs() []SignKeyPair -} - // DataWithSignature is an interface of data-signature pair with read access. type DataWithSignature interface { SignedDataSource SignatureSource } + +// DataWithSignKeyAccumulator is an interface of data and key-signature accumulator pair. +type DataWithSignKeyAccumulator interface { + SignedDataSource + SignKeyPairAccumulator +} + +// DataWithSignKeySource is an interface of data and key-signature source pair. +type DataWithSignKeySource interface { + SignedDataSource + SignKeyPairSource +} + +// SignedDataWithToken is an interface of data-token pair with read access. +type SignedDataWithToken interface { + SignedDataSource + SessionTokenSource +} + +// DataWithTokenSignAccumulator is an interface of data-token pair with signature write access. +type DataWithTokenSignAccumulator interface { + SignedDataWithToken + SignKeyPairAccumulator +} + +// DataWithTokenSignSource is an interface of data-token pair with signature read access. +type DataWithTokenSignSource interface { + SignedDataWithToken + SignKeyPairSource +} From 082edf745626b0731c300ee7b7731d88aebc01c2 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 12:50:15 +0300 Subject: [PATCH 11/19] service: implement sign-verify methods on RequestVerificationHeader --- service/verify.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/service/verify.go b/service/verify.go index 3ed8402a..beca9925 100644 --- a/service/verify.go +++ b/service/verify.go @@ -35,6 +35,56 @@ type ( } ) +// GetSessionToken returns SessionToken interface of Token field. +// +// If token field value is nil, nil returns. +func (m RequestVerificationHeader) GetSessionToken() SessionToken { + if t := m.GetToken(); t != nil { + return t + } + + return nil +} + +// AddSignKey adds new element to Signatures field. +// +// Sets Sign field to passed sign. Set Peer field to marshaled passed key. +func (m *RequestVerificationHeader) AddSignKey(sign []byte, key *ecdsa.PublicKey) { + m.SetSignatures( + append( + m.GetSignatures(), + &RequestVerificationHeader_Signature{ + Sign: sign, + Peer: crypto.MarshalPublicKey(key), + }, + ), + ) +} + +// GetSignKeyPairs returns the elements of Signatures field as SignKeyPair slice. +func (m RequestVerificationHeader) GetSignKeyPairs() []SignKeyPair { + var ( + signs = m.GetSignatures() + res = make([]SignKeyPair, len(signs)) + ) + + for i := range signs { + res[i] = signs[i] + } + + return res +} + +// GetSignature returns the result of a Sign field getter. +func (m RequestVerificationHeader_Signature) GetSignature() []byte { + return m.GetSign() +} + +// GetPublicKey unmarshals and returns the result of a Peer field getter. +func (m RequestVerificationHeader_Signature) GetPublicKey() *ecdsa.PublicKey { + return crypto.UnmarshalPublicKey(m.GetPeer()) +} + // SetSignatures replaces signatures stored in RequestVerificationHeader. func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificationHeader_Signature) { m.Signatures = signatures From 78f435a9051f0cdcdf9ec6faf81bc8cbd8cb2e44 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 13:16:15 +0300 Subject: [PATCH 12/19] object: implement signing payload methods on PutRequest message --- object/sign.go | 45 ++++++++++++++++++++++++++ object/sign_test.go | 79 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 object/sign.go create mode 100644 object/sign_test.go diff --git a/object/sign.go b/object/sign.go new file mode 100644 index 00000000..1dc5bd0e --- /dev/null +++ b/object/sign.go @@ -0,0 +1,45 @@ +package object + +// SignedData returns marshaled payload of the Put request. +// +// If payload is nil, ErrHeaderNotFound returns. +func (m PutRequest) SignedData() ([]byte, error) { + r := m.GetR() + if r == nil { + return nil, ErrHeaderNotFound + } + + data := make([]byte, r.Size()) + + if _, err := r.MarshalTo(data); err != nil { + return nil, err + } + + return data, nil +} + +// ReadSignedData copies marshaled payload of the Put request to passed buffer. +// +// If payload is nil, ErrHeaderNotFound returns. +func (m PutRequest) ReadSignedData(p []byte) error { + r := m.GetR() + if r == nil { + return ErrHeaderNotFound + } + + _, err := r.MarshalTo(p) + + return err +} + +// SignedDataSize returns the size of payload of the Put request. +// +// If payload is nil, -1 returns. +func (m PutRequest) SignedDataSize() int { + r := m.GetR() + if r == nil { + return -1 + } + + return r.Size() +} diff --git a/object/sign_test.go b/object/sign_test.go new file mode 100644 index 00000000..2574d9cc --- /dev/null +++ b/object/sign_test.go @@ -0,0 +1,79 @@ +package object + +import ( + "testing" + + "github.com/nspcc-dev/neofs-api-go/service" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/stretchr/testify/require" +) + +func TestSignVerifyRequests(t *testing.T) { + sk := test.DecodeKey(0) + + type sigType interface { + service.SignedDataWithToken + service.SignKeyPairAccumulator + service.SignKeyPairSource + SetToken(*Token) + } + + items := []struct { + constructor func() sigType + bodyCorrupt []func(sigType) + }{ + { // PutRequest.PutHeader + constructor: func() sigType { + return MakePutRequestHeader(new(Object)) + }, + bodyCorrupt: []func(sigType){ + func(s sigType) { + obj := s.(*PutRequest).GetR().(*PutRequest_Header).Header.GetObject() + obj.SystemHeader.PayloadLength++ + }, + }, + }, + { // PutRequest.Chunk + constructor: func() sigType { + return MakePutRequestChunk(make([]byte, 10)) + }, + bodyCorrupt: []func(sigType){ + func(s sigType) { + h := s.(*PutRequest).GetR().(*PutRequest_Chunk) + h.Chunk[0]++ + }, + }, + }, + } + + for _, item := range items { + { // token corruptions + v := item.constructor() + + token := new(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)) + } + + { // body corruptions + for _, corruption := range item.bodyCorrupt { + v := item.constructor() + + require.NoError(t, service.SignDataWithSessionToken(sk, v)) + + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(v)) + + corruption(v) + + require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) + } + } + } +} From 439221cea824f2b5c1568160db37344fac219a8f Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 13:33:03 +0300 Subject: [PATCH 13/19] object: implement signing payload methods on GetRequest message --- object/sign.go | 40 ++++++++++++++++++++++++++++++++++++++++ object/sign_test.go | 25 +++++++++++++++++++------ 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/object/sign.go b/object/sign.go index 1dc5bd0e..63687a51 100644 --- a/object/sign.go +++ b/object/sign.go @@ -1,5 +1,9 @@ package object +import ( + "io" +) + // SignedData returns marshaled payload of the Put request. // // If payload is nil, ErrHeaderNotFound returns. @@ -43,3 +47,39 @@ func (m PutRequest) SignedDataSize() int { return r.Size() } + +// SignedData returns marshaled Address field. +// +// Resulting error is always nil. +func (m GetRequest) SignedData() ([]byte, error) { + addr := m.GetAddress() + + return append( + addr.CID.Bytes(), + addr.ObjectID.Bytes()..., + ), nil +} + +// ReadSignedData copies marshaled Address field to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetRequest) ReadSignedData(p []byte) error { + addr := m.GetAddress() + + if len(p) < addr.CID.Size()+addr.ObjectID.Size() { + return io.ErrUnexpectedEOF + } + + off := copy(p, addr.CID.Bytes()) + + copy(p[off:], addr.ObjectID.Bytes()) + + return nil +} + +// SignedDataSize returns the size of object address. +func (m GetRequest) SignedDataSize() int { + addr := m.GetAddress() + + return addr.CID.Size() + addr.ObjectID.Size() +} diff --git a/object/sign_test.go b/object/sign_test.go index 2574d9cc..8cf5627d 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -19,14 +19,14 @@ func TestSignVerifyRequests(t *testing.T) { } items := []struct { - constructor func() sigType - bodyCorrupt []func(sigType) + constructor func() sigType + payloadCorrupt []func(sigType) }{ { // PutRequest.PutHeader constructor: func() sigType { return MakePutRequestHeader(new(Object)) }, - bodyCorrupt: []func(sigType){ + payloadCorrupt: []func(sigType){ func(s sigType) { obj := s.(*PutRequest).GetR().(*PutRequest_Header).Header.GetObject() obj.SystemHeader.PayloadLength++ @@ -37,13 +37,26 @@ func TestSignVerifyRequests(t *testing.T) { constructor: func() sigType { return MakePutRequestChunk(make([]byte, 10)) }, - bodyCorrupt: []func(sigType){ + payloadCorrupt: []func(sigType){ func(s sigType) { h := s.(*PutRequest).GetR().(*PutRequest_Chunk) h.Chunk[0]++ }, }, }, + { // GetRequest + constructor: func() sigType { + return new(GetRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*GetRequest).Address.CID[0]++ + }, + func(s sigType) { + s.(*GetRequest).Address.ObjectID[0]++ + }, + }, + }, } for _, item := range items { @@ -62,8 +75,8 @@ func TestSignVerifyRequests(t *testing.T) { require.Error(t, service.VerifyAccumulatedSignaturesWithToken(v)) } - { // body corruptions - for _, corruption := range item.bodyCorrupt { + { // payload corruptions + for _, corruption := range item.payloadCorrupt { v := item.constructor() require.NoError(t, service.SignDataWithSessionToken(sk, v)) From 68f83f547043edaba1e075414cd511f5163ebacc Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 13:54:13 +0300 Subject: [PATCH 14/19] object: implement signing payload methods on HeadRequest message --- object/sign.go | 56 ++++++++++++++++++++++++++++++++++++++++----- object/sign_test.go | 16 +++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/object/sign.go b/object/sign.go index 63687a51..6cece1ba 100644 --- a/object/sign.go +++ b/object/sign.go @@ -54,10 +54,7 @@ func (m PutRequest) SignedDataSize() int { func (m GetRequest) SignedData() ([]byte, error) { addr := m.GetAddress() - return append( - addr.CID.Bytes(), - addr.ObjectID.Bytes()..., - ), nil + return addressBytes(addr), nil } // ReadSignedData copies marshaled Address field to passed buffer. @@ -66,7 +63,7 @@ func (m GetRequest) SignedData() ([]byte, error) { func (m GetRequest) ReadSignedData(p []byte) error { addr := m.GetAddress() - if len(p) < addr.CID.Size()+addr.ObjectID.Size() { + if len(p) < m.SignedDataSize() { return io.ErrUnexpectedEOF } @@ -79,7 +76,54 @@ func (m GetRequest) ReadSignedData(p []byte) error { // SignedDataSize returns the size of object address. func (m GetRequest) SignedDataSize() int { - addr := m.GetAddress() + return addressSize(m.GetAddress()) +} +// SignedData returns marshaled Address field. +// +// Resulting error is always nil. +func (m HeadRequest) SignedData() ([]byte, error) { + sz := addressSize(m.Address) + + data := make([]byte, sz+1) + + if m.GetFullHeaders() { + data[0] = 1 + } + + copy(data[1:], addressBytes(m.Address)) + + return data, nil +} + +// ReadSignedData copies marshaled Address field to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m HeadRequest) ReadSignedData(p []byte) error { + if len(p) < m.SignedDataSize() { + return io.ErrUnexpectedEOF + } + + if m.GetFullHeaders() { + p[0] = 1 + } + + off := 1 + copy(p[1:], m.Address.CID.Bytes()) + + copy(p[off:], m.Address.ObjectID.Bytes()) + + return nil +} + +// SignedDataSize returns the size of object address. +func (m HeadRequest) SignedDataSize() int { + return addressSize(m.Address) + 1 +} + +func addressSize(addr Address) int { return addr.CID.Size() + addr.ObjectID.Size() } + +func addressBytes(addr Address) []byte { + return append(addr.CID.Bytes(), addr.ObjectID.Bytes()...) +} diff --git a/object/sign_test.go b/object/sign_test.go index 8cf5627d..c5159c63 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -57,6 +57,22 @@ func TestSignVerifyRequests(t *testing.T) { }, }, }, + { // HeadRequest + constructor: func() sigType { + return new(HeadRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*HeadRequest).Address.CID[0]++ + }, + func(s sigType) { + s.(*HeadRequest).Address.ObjectID[0]++ + }, + func(s sigType) { + s.(*HeadRequest).FullHeaders = true + }, + }, + }, } for _, item := range items { From fc0da3c8fcb69e49a9e26cea3b482ff3102381d1 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 14:15:07 +0300 Subject: [PATCH 15/19] object: implement signing payload methods on DeleteRequest message --- object/sign.go | 77 ++++++++++++++++++++++++++------------------- object/sign_test.go | 16 ++++++++++ 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/object/sign.go b/object/sign.go index 6cece1ba..acb9901c 100644 --- a/object/sign.go +++ b/object/sign.go @@ -4,27 +4,23 @@ import ( "io" ) -// SignedData returns marshaled payload of the Put request. +// SignedData returns payload bytes of the request. // // If payload is nil, ErrHeaderNotFound returns. func (m PutRequest) SignedData() ([]byte, error) { - r := m.GetR() - if r == nil { + sz := m.SignedDataSize() + if sz < 0 { return nil, ErrHeaderNotFound } - data := make([]byte, r.Size()) + data := make([]byte, sz) - if _, err := r.MarshalTo(data); err != nil { - return nil, err - } - - return data, nil + return data, m.ReadSignedData(data) } -// ReadSignedData copies marshaled payload of the Put request to passed buffer. +// ReadSignedData copies payload bytes to passed buffer. // -// If payload is nil, ErrHeaderNotFound returns. +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. func (m PutRequest) ReadSignedData(p []byte) error { r := m.GetR() if r == nil { @@ -48,16 +44,14 @@ func (m PutRequest) SignedDataSize() int { return r.Size() } -// SignedData returns marshaled Address field. -// -// Resulting error is always nil. +// SignedData returns payload bytes of the request. func (m GetRequest) SignedData() ([]byte, error) { - addr := m.GetAddress() + data := make([]byte, m.SignedDataSize()) - return addressBytes(addr), nil + return data, m.ReadSignedData(data) } -// ReadSignedData copies marshaled Address field to passed buffer. +// ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. func (m GetRequest) ReadSignedData(p []byte) error { @@ -74,29 +68,19 @@ func (m GetRequest) ReadSignedData(p []byte) error { return nil } -// SignedDataSize returns the size of object address. +// SignedDataSize returns payload size of the request. func (m GetRequest) SignedDataSize() int { return addressSize(m.GetAddress()) } -// SignedData returns marshaled Address field. -// -// Resulting error is always nil. +// SignedData returns payload bytes of the request. func (m HeadRequest) SignedData() ([]byte, error) { - sz := addressSize(m.Address) + data := make([]byte, m.SignedDataSize()) - data := make([]byte, sz+1) - - if m.GetFullHeaders() { - data[0] = 1 - } - - copy(data[1:], addressBytes(m.Address)) - - return data, nil + return data, m.ReadSignedData(data) } -// ReadSignedData copies marshaled Address field to passed buffer. +// ReadSignedData copies payload bytes to passed buffer. // // If the buffer size is insufficient, io.ErrUnexpectedEOF returns. func (m HeadRequest) ReadSignedData(p []byte) error { @@ -115,11 +99,38 @@ func (m HeadRequest) ReadSignedData(p []byte) error { return nil } -// SignedDataSize returns the size of object address. +// SignedDataSize returns payload size of the request. func (m HeadRequest) SignedDataSize() int { return addressSize(m.Address) + 1 } +// SignedData returns payload bytes of the request. +func (m DeleteRequest) SignedData() ([]byte, error) { + data := make([]byte, m.SignedDataSize()) + + return data, m.ReadSignedData(data) +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m DeleteRequest) ReadSignedData(p []byte) error { + if len(p) < m.SignedDataSize() { + return io.ErrUnexpectedEOF + } + + off := copy(p, m.OwnerID.Bytes()) + + copy(p[off:], addressBytes(m.Address)) + + return nil +} + +// SignedDataSize returns payload size of the request. +func (m DeleteRequest) SignedDataSize() int { + return m.OwnerID.Size() + addressSize(m.Address) +} + func addressSize(addr Address) int { return addr.CID.Size() + addr.ObjectID.Size() } diff --git a/object/sign_test.go b/object/sign_test.go index c5159c63..d6357b9c 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -73,6 +73,22 @@ func TestSignVerifyRequests(t *testing.T) { }, }, }, + { // DeleteRequest + constructor: func() sigType { + return new(DeleteRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*DeleteRequest).OwnerID[0]++ + }, + func(s sigType) { + s.(*DeleteRequest).Address.CID[0]++ + }, + func(s sigType) { + s.(*DeleteRequest).Address.ObjectID[0]++ + }, + }, + }, } for _, item := range items { From e784206032922a82fc5180d68d08e69572701406 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 14:38:39 +0300 Subject: [PATCH 16/19] object: implement signing payload methods on GetRangeRequest message --- object/sign.go | 30 ++++++++++++++++++++++++++++++ object/sign_test.go | 19 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/object/sign.go b/object/sign.go index acb9901c..506fc951 100644 --- a/object/sign.go +++ b/object/sign.go @@ -131,6 +131,36 @@ func (m DeleteRequest) SignedDataSize() int { return m.OwnerID.Size() + addressSize(m.Address) } +// SignedData returns payload bytes of the request. +func (m GetRangeRequest) SignedData() ([]byte, error) { + data := make([]byte, m.SignedDataSize()) + + return data, m.ReadSignedData(data) +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetRangeRequest) ReadSignedData(p []byte) error { + if len(p) < m.SignedDataSize() { + return io.ErrUnexpectedEOF + } + + n, err := (&m.Range).MarshalTo(p) + if err != nil { + return err + } + + copy(p[n:], addressBytes(m.GetAddress())) + + return nil +} + +// SignedDataSize returns payload size of the request. +func (m GetRangeRequest) SignedDataSize() int { + return (&m.Range).Size() + addressSize(m.GetAddress()) +} + func addressSize(addr Address) int { return addr.CID.Size() + addr.ObjectID.Size() } diff --git a/object/sign_test.go b/object/sign_test.go index d6357b9c..f8450ef9 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -89,6 +89,25 @@ func TestSignVerifyRequests(t *testing.T) { }, }, }, + { // GetRangeRequest + constructor: func() sigType { + return new(GetRangeRequest) + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*GetRangeRequest).Range.Length++ + }, + func(s sigType) { + s.(*GetRangeRequest).Range.Offset++ + }, + func(s sigType) { + s.(*GetRangeRequest).Address.CID[0]++ + }, + func(s sigType) { + s.(*GetRangeRequest).Address.ObjectID[0]++ + }, + }, + }, } for _, item := range items { From 84671cd4aa49eaa0947a2774d8d1ac0857535806 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 14:58:29 +0300 Subject: [PATCH 17/19] object: implement signing payload methods on GetRangeHashRequest message --- object/sign.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ object/sign_test.go | 28 +++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/object/sign.go b/object/sign.go index 506fc951..3b97d621 100644 --- a/object/sign.go +++ b/object/sign.go @@ -1,6 +1,7 @@ package object import ( + "encoding/binary" "io" ) @@ -161,6 +162,81 @@ func (m GetRangeRequest) SignedDataSize() int { return (&m.Range).Size() + addressSize(m.GetAddress()) } +// SignedData returns payload bytes of the request. +func (m GetRangeHashRequest) SignedData() ([]byte, error) { + data := make([]byte, m.SignedDataSize()) + + return data, m.ReadSignedData(data) +} + +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m GetRangeHashRequest) ReadSignedData(p []byte) error { + if len(p) < m.SignedDataSize() { + return io.ErrUnexpectedEOF + } + + var off int + + off += copy(p[off:], addressBytes(m.GetAddress())) + + off += copy(p[off:], sliceBytes(m.GetSalt())) + + copy(p[off:], rangeSetBytes(m.GetRanges())) + + return nil +} + +// SignedDataSize returns payload size of the request. +func (m GetRangeHashRequest) SignedDataSize() int { + var sz int + + sz += addressSize(m.GetAddress()) + + sz += rangeSetSize(m.GetRanges()) + + sz += sliceSize(m.GetSalt()) + + return sz +} + +func sliceSize(v []byte) int { + return 4 + len(v) +} + +func sliceBytes(v []byte) []byte { + data := make([]byte, sliceSize(v)) + + binary.BigEndian.PutUint32(data, uint32(len(v))) + + copy(data[4:], v) + + return data +} + +func rangeSetSize(rs []Range) int { + return 4 + len(rs)*16 // two uint64 fields +} + +func rangeSetBytes(rs []Range) []byte { + data := make([]byte, rangeSetSize(rs)) + + binary.BigEndian.PutUint32(data, uint32(len(rs))) + + off := 4 + + for i := range rs { + binary.BigEndian.PutUint64(data[off:], rs[i].Offset) + off += 8 + + binary.BigEndian.PutUint64(data[off:], rs[i].Length) + off += 8 + } + + return data +} + func addressSize(addr Address) int { return addr.CID.Size() + addr.ObjectID.Size() } diff --git a/object/sign_test.go b/object/sign_test.go index f8450ef9..8b9a0119 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -108,6 +108,34 @@ func TestSignVerifyRequests(t *testing.T) { }, }, }, + { // GetRangeHashRequest + constructor: func() sigType { + return &GetRangeHashRequest{ + Ranges: []Range{{}}, + Salt: []byte{1, 2, 3}, + } + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*GetRangeHashRequest).Address.CID[0]++ + }, + func(s sigType) { + s.(*GetRangeHashRequest).Address.ObjectID[0]++ + }, + func(s sigType) { + s.(*GetRangeHashRequest).Salt[0]++ + }, + func(s sigType) { + s.(*GetRangeHashRequest).Ranges[0].Length++ + }, + func(s sigType) { + s.(*GetRangeHashRequest).Ranges[0].Offset++ + }, + func(s sigType) { + s.(*GetRangeHashRequest).Ranges = nil + }, + }, + }, } for _, item := range items { From 4aac4d093de8b20a2f58acf9d982266c285ad29d Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 15:09:31 +0300 Subject: [PATCH 18/19] object: implement signing payload methods on SearchRequest message --- object/sign.go | 46 +++++++++++++++++++++++++++++++++++---------- object/sign_test.go | 18 ++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/object/sign.go b/object/sign.go index 3b97d621..25d0b2f4 100644 --- a/object/sign.go +++ b/object/sign.go @@ -181,9 +181,9 @@ func (m GetRangeHashRequest) ReadSignedData(p []byte) error { off += copy(p[off:], addressBytes(m.GetAddress())) - off += copy(p[off:], sliceBytes(m.GetSalt())) + off += copy(p[off:], rangeSetBytes(m.GetRanges())) - copy(p[off:], rangeSetBytes(m.GetRanges())) + off += copy(p[off:], m.GetSalt()) return nil } @@ -196,23 +196,49 @@ func (m GetRangeHashRequest) SignedDataSize() int { sz += rangeSetSize(m.GetRanges()) - sz += sliceSize(m.GetSalt()) + sz += len(m.GetSalt()) return sz } -func sliceSize(v []byte) int { - return 4 + len(v) +// SignedData returns payload bytes of the request. +func (m SearchRequest) SignedData() ([]byte, error) { + data := make([]byte, m.SignedDataSize()) + + return data, m.ReadSignedData(data) } -func sliceBytes(v []byte) []byte { - data := make([]byte, sliceSize(v)) +// ReadSignedData copies payload bytes to passed buffer. +// +// If the buffer size is insufficient, io.ErrUnexpectedEOF returns. +func (m SearchRequest) ReadSignedData(p []byte) error { + if len(p) < m.SignedDataSize() { + return io.ErrUnexpectedEOF + } - binary.BigEndian.PutUint32(data, uint32(len(v))) + var off int - copy(data[4:], v) + off += copy(p[off:], m.CID().Bytes()) - return data + binary.BigEndian.PutUint32(p[off:], m.GetQueryVersion()) + off += 4 + + copy(p[off:], m.GetQuery()) + + return nil +} + +// SignedDataSize returns payload size of the request. +func (m SearchRequest) SignedDataSize() int { + var sz int + + sz += m.CID().Size() + + sz += 4 // uint32 Version + + sz += len(m.GetQuery()) + + return sz } func rangeSetSize(rs []Range) int { diff --git a/object/sign_test.go b/object/sign_test.go index 8b9a0119..4df1c2b1 100644 --- a/object/sign_test.go +++ b/object/sign_test.go @@ -136,6 +136,24 @@ func TestSignVerifyRequests(t *testing.T) { }, }, }, + { // GetRangeHashRequest + constructor: func() sigType { + return &SearchRequest{ + Query: []byte{1, 2, 3}, + } + }, + payloadCorrupt: []func(sigType){ + func(s sigType) { + s.(*SearchRequest).ContainerID[0]++ + }, + func(s sigType) { + s.(*SearchRequest).Query[0]++ + }, + func(s sigType) { + s.(*SearchRequest).QueryVersion++ + }, + }, + }, } for _, item := range items { From 65d7c39e1a54d488f341eef33bab801d47c870f2 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 6 May 2020 15:32:13 +0300 Subject: [PATCH 19/19] service: fix comments --- service/meta.go | 2 +- service/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/service/meta.go b/service/meta.go index 3a945aca..3f017584 100644 --- a/service/meta.go +++ b/service/meta.go @@ -1,6 +1,6 @@ package service -// ResetMeta returns current value and sets RequestMetaHeader to empty value. +// CutMeta returns current value and sets RequestMetaHeader to empty value. func (m *RequestMetaHeader) CutMeta() RequestMetaHeader { cp := *m m.Reset() diff --git a/service/types.go b/service/types.go index 020cba0d..c3148a03 100644 --- a/service/types.go +++ b/service/types.go @@ -22,7 +22,7 @@ type RawContainer interface { SetRaw(bool) } -// VersionContainer is an interface of the container of a numerical Version value with read access. +// VersionSource is an interface of the container of a numerical Version value with read access. type VersionSource interface { GetVersion() uint32 }