diff --git a/service/sign.go b/service/sign.go index f5cdc0b..5b1548f 100644 --- a/service/sign.go +++ b/service/sign.go @@ -2,6 +2,7 @@ package service import ( "crypto/ecdsa" + "sync" crypto "github.com/nspcc-dev/neofs-crypto" ) @@ -11,6 +12,12 @@ type keySign struct { sign []byte } +var bytesPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 5<<20) + }, +} + // GetSignature is a sign field getter. func (s keySign) GetSignature() []byte { return s.sign diff --git a/service/verify.go b/service/verify.go index beca992..62db2f5 100644 --- a/service/verify.go +++ b/service/verify.go @@ -2,37 +2,9 @@ package service import ( "crypto/ecdsa" - "sync" "github.com/nspcc-dev/neofs-api-go/internal" - "github.com/nspcc-dev/neofs-api-go/refs" crypto "github.com/nspcc-dev/neofs-crypto" - "github.com/pkg/errors" -) - -type ( - // VerifiableRequest adds possibility to sign and verify request header. - VerifiableRequest interface { - Size() int - MarshalTo([]byte) (int, error) - AddSignature(*RequestVerificationHeader_Signature) - GetSignatures() []*RequestVerificationHeader_Signature - SetSignatures([]*RequestVerificationHeader_Signature) - } - - // MaintainableRequest adds possibility to set and get (+validate) - // owner (client) public key from RequestVerificationHeader. - MaintainableRequest interface { - GetOwner() (*ecdsa.PublicKey, error) - SetOwner(*ecdsa.PublicKey, []byte) - GetLastPeer() (*ecdsa.PublicKey, error) - } - - // TokenHeader is an interface of the container of a Token pointer value - TokenHeader interface { - GetToken() *Token - SetToken(*Token) - } ) // GetSessionToken returns SessionToken interface of Token field. @@ -90,164 +62,11 @@ func (m *RequestVerificationHeader) SetSignatures(signatures []*RequestVerificat m.Signatures = signatures } -// AddSignature adds new Signature into RequestVerificationHeader. -func (m *RequestVerificationHeader) AddSignature(sig *RequestVerificationHeader_Signature) { - if sig == nil { - return - } - m.Signatures = append(m.Signatures, sig) -} - -// CheckOwner validates, that passed OwnerID is equal to present PublicKey of owner. -func (m *RequestVerificationHeader) CheckOwner(owner refs.OwnerID) error { - if key, err := m.GetOwner(); err != nil { - return err - } else if user, err := refs.NewOwnerID(key); err != nil { - return err - } else if !user.Equal(owner) { - return ErrWrongOwner - } - return nil -} - -// GetOwner tries to get owner (client) public key from signatures. -// If signatures contains not empty Origin, we should try to validate, -// that session key was signed by owner (client), otherwise return error. -func (m *RequestVerificationHeader) GetOwner() (*ecdsa.PublicKey, error) { - if len(m.Signatures) == 0 { - return nil, ErrCannotFindOwner - } else if key := crypto.UnmarshalPublicKey(m.Signatures[0].Peer); key != nil { - return key, nil - } - - return nil, ErrInvalidPublicKeyBytes -} - -// GetLastPeer tries to get last peer public key from signatures. -// If signatures has zero length, returns ErrCannotFindOwner. -// If signatures has length equal to one, uses GetOwner. -// Otherwise tries to unmarshal last peer public key. -func (m *RequestVerificationHeader) GetLastPeer() (*ecdsa.PublicKey, error) { - switch ln := len(m.Signatures); ln { - case 0: - return nil, ErrCannotFindOwner - case 1: - return m.GetOwner() - default: - if key := crypto.UnmarshalPublicKey(m.Signatures[ln-1].Peer); key != nil { - return key, nil - } - - return nil, ErrInvalidPublicKeyBytes - } -} - // SetToken is a Token field setter. func (m *RequestVerificationHeader) SetToken(token *Token) { m.Token = token } -func newSignature(key *ecdsa.PrivateKey, data []byte) (*RequestVerificationHeader_Signature, error) { - sign, err := crypto.Sign(key, data) - if err != nil { - return nil, err - } - - return &RequestVerificationHeader_Signature{ - Sign: sign, - Peer: crypto.MarshalPublicKey(&key.PublicKey), - }, nil -} - -var bytesPool = sync.Pool{New: func() interface{} { - return make([]byte, 4.5*1024*1024) // 4.5MB -}} - -// SignRequestHeader receives private key and request with RequestVerificationHeader, -// tries to marshal and sign request with passed PrivateKey, after that adds -// 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.(SeizedRequestMetaContainer); ok { - h := meta.CutMeta() - - defer func() { - meta.RestoreMeta(h) - }() - } - - data := bytesPool.Get().([]byte) - defer func() { - bytesPool.Put(data) - }() - - if size := msg.Size(); size <= cap(data) { - data = data[:size] - } else { - data = make([]byte, size) - } - - size, err := msg.MarshalTo(data) - if err != nil { - return err - } - - signature, err := newSignature(key, data[:size]) - if err != nil { - return err - } - - msg.AddSignature(signature) - - return nil -} - -// VerifyRequestHeader receives request with RequestVerificationHeader, -// tries to marshal and verify each signature from request. -// If something went wrong, returns error. -func VerifyRequestHeader(msg VerifiableRequest) error { - // ignore meta header - if meta, ok := msg.(SeizedRequestMetaContainer); ok { - h := meta.CutMeta() - - defer func() { - meta.RestoreMeta(h) - }() - } - - data := bytesPool.Get().([]byte) - signatures := msg.GetSignatures() - defer func() { - bytesPool.Put(data) - msg.SetSignatures(signatures) - }() - - for i := range signatures { - msg.SetSignatures(signatures[:i]) - peer := signatures[i].GetPeer() - sign := signatures[i].GetSign() - - key := crypto.UnmarshalPublicKey(peer) - if key == nil { - return errors.Wrapf(ErrInvalidPublicKeyBytes, "%d: %02x", i, peer) - } - - if size := msg.Size(); size <= cap(data) { - data = data[:size] - } else { - data = make([]byte, size) - } - - if size, err := msg.MarshalTo(data); err != nil { - return errors.Wrapf(err, "%d: %02x", i, peer) - } else if err := crypto.Verify(key, data[:size], sign); err != nil { - return errors.Wrapf(err, "%d: %02x", i, peer) - } - } - - return nil -} - // testCustomField for test usage only. type testCustomField [8]uint32 diff --git a/service/verify_test.go b/service/verify_test.go index 107416b..c6e4d61 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -1,194 +1,107 @@ package service import ( - "bytes" - "log" + "encoding/binary" + "io" "math" "testing" - "github.com/gogo/protobuf/proto" "github.com/nspcc-dev/neofs-api-go/refs" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-crypto/test" - "github.com/pkg/errors" "github.com/stretchr/testify/require" ) -func BenchmarkSignRequestHeader(b *testing.B) { +func (m TestRequest) SignedData() ([]byte, error) { + return SignedDataFromReader(m) +} + +func (m TestRequest) SignedDataSize() (sz int) { + sz += 4 + + sz += len(m.StringField) + + sz += len(m.BytesField) + + sz += m.CustomField.Size() + + return +} + +func (m TestRequest) ReadSignedData(p []byte) (int, error) { + if len(p) < m.SignedDataSize() { + return 0, io.ErrUnexpectedEOF + } + + var off int + + binary.BigEndian.PutUint32(p[off:], uint32(m.IntField)) + off += 4 + + off += copy(p[off:], []byte(m.StringField)) + + off += copy(p[off:], m.BytesField) + + n, err := m.CustomField.MarshalTo(p[off:]) + off += n + + return off, err +} + +func BenchmarkSignDataWithSessionToken(b *testing.B) { key := test.DecodeKey(0) - custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + customField := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} - some := &TestRequest{ + token := new(Token) + + req := &TestRequest{ IntField: math.MaxInt32, StringField: "TestRequestStringField", BytesField: make([]byte, 1<<22), - CustomField: &custom, - RequestMetaHeader: RequestMetaHeader{ - TTL: math.MaxInt32 - 8, - Epoch: math.MaxInt64 - 12, - }, + CustomField: &customField, + } + + req.SetTTL(math.MaxInt32 - 8) + req.SetEpoch(math.MaxInt64 - 12) + req.SetToken(token) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, SignDataWithSessionToken(key, req)) + } +} + +func BenchmarkVerifyAccumulatedSignaturesWithToken(b *testing.B) { + customField := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + + token := new(Token) + + req := &TestRequest{ + IntField: math.MaxInt32, + StringField: "TestRequestStringField", + BytesField: make([]byte, 1<<22), + CustomField: &customField, + } + + req.SetTTL(math.MaxInt32 - 8) + req.SetEpoch(math.MaxInt64 - 12) + req.SetToken(token) + + for i := 0; i < 10; i++ { + key := test.DecodeKey(i) + require.NoError(b, SignDataWithSessionToken(key, req)) } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - require.NoError(b, SignRequestHeader(key, some)) + require.NoError(b, VerifyAccumulatedSignaturesWithToken(req)) } } -func BenchmarkVerifyRequestHeader(b *testing.B) { - custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} - - some := &TestRequest{ - IntField: math.MaxInt32, - StringField: "TestRequestStringField", - BytesField: make([]byte, 1<<22), - CustomField: &custom, - RequestMetaHeader: RequestMetaHeader{ - TTL: math.MaxInt32 - 8, - Epoch: math.MaxInt64 - 12, - }, - } - - for i := 0; i < 10; i++ { - key := test.DecodeKey(i) - require.NoError(b, SignRequestHeader(key, some)) - } - - b.ResetTimer() - b.ReportAllocs() - - for i := 0; i < b.N; i++ { - require.NoError(b, VerifyRequestHeader(some)) - } -} - -func TestSignRequestHeader(t *testing.T) { - req := &TestRequest{ - IntField: math.MaxInt32, - StringField: "TestRequestStringField", - BytesField: []byte("TestRequestBytesField"), - } - - key := test.DecodeKey(0) - peer := crypto.MarshalPublicKey(&key.PublicKey) - - data, err := req.Marshal() - require.NoError(t, err) - - require.NoError(t, SignRequestHeader(key, req)) - - require.Len(t, req.Signatures, 1) - for i := range req.Signatures { - sign := req.Signatures[i].GetSign() - require.Equal(t, peer, req.Signatures[i].GetPeer()) - require.NoError(t, crypto.Verify(&key.PublicKey, data, sign)) - } -} - -func TestVerifyRequestHeader(t *testing.T) { - req := &TestRequest{ - IntField: math.MaxInt32, - StringField: "TestRequestStringField", - BytesField: []byte("TestRequestBytesField"), - RequestMetaHeader: RequestMetaHeader{TTL: 10}, - } - - for i := 0; i < 10; i++ { - req.TTL-- - require.NoError(t, SignRequestHeader(test.DecodeKey(i), req)) - } - - require.NoError(t, VerifyRequestHeader(req)) -} - -func TestMaintainableRequest(t *testing.T) { - req := &TestRequest{ - IntField: math.MaxInt32, - StringField: "TestRequestStringField", - BytesField: []byte("TestRequestBytesField"), - RequestMetaHeader: RequestMetaHeader{TTL: 10}, - } - - count := 10 - owner := test.DecodeKey(count + 1) - - for i := 0; i < count; i++ { - req.TTL-- - - key := test.DecodeKey(i) - - // sign first key (session key) by owner key - if i == 0 { - key = owner - } - - require.NoError(t, SignRequestHeader(key, req)) - } - - { // Validate owner - user, err := refs.NewOwnerID(&owner.PublicKey) - require.NoError(t, err) - require.NoError(t, req.CheckOwner(user)) - } - - { // Good case: - require.NoError(t, VerifyRequestHeader(req)) - - // validate, that first key (session key) was signed with owner - signatures := req.GetSignatures() - - require.Len(t, signatures, count) - - pub, err := req.GetOwner() - require.NoError(t, err) - - require.Equal(t, &owner.PublicKey, pub) - } - - { // Wrong signatures: - copy(req.Signatures[count-1].Sign, req.Signatures[count-2].Sign) - err := VerifyRequestHeader(req) - require.EqualError(t, errors.Cause(err), crypto.ErrInvalidSignature.Error()) - } -} - -func TestVerifyAndSignRequestHeaderWithoutCloning(t *testing.T) { - key := test.DecodeKey(0) - - custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} - - b := &TestRequest{ - IntField: math.MaxInt32, - StringField: "TestRequestStringField", - BytesField: []byte("TestRequestBytesField"), - CustomField: &custom, - RequestMetaHeader: RequestMetaHeader{ - TTL: math.MaxInt32 - 8, - Epoch: math.MaxInt64 - 12, - }, - } - - require.NoError(t, SignRequestHeader(key, b)) - require.NoError(t, VerifyRequestHeader(b)) - - require.Len(t, b.Signatures, 1) - require.Equal(t, custom, *b.CustomField) - require.Equal(t, uint32(math.MaxInt32-8), b.GetTTL()) - require.Equal(t, uint64(math.MaxInt64-12), b.GetEpoch()) - - buf := bytes.NewBuffer(nil) - log.SetOutput(buf) - - cp, ok := proto.Clone(b).(*TestRequest) - require.True(t, ok) - require.NotEqual(t, b, cp) - - require.Contains(t, buf.String(), "proto: don't know how to copy") -} - func TestRequestVerificationHeader_SetToken(t *testing.T) { id, err := refs.NewUUID() require.NoError(t, err)