From 5736b834c3d56f460630082e050989da300fdfe6 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Mon, 4 Apr 2022 16:04:18 +0300 Subject: [PATCH] [#1286] services/object: Validate object when created with sesssion token All fields set by a user should be verified. Signed-off-by: Evgenii Stratonikov --- pkg/core/object/fmt.go | 26 ++++---- pkg/core/object/fmt_test.go | 18 +++--- pkg/services/object/put/streamer.go | 27 ++++---- pkg/services/object/put/validation.go | 89 +++++++++++++++------------ 4 files changed, 89 insertions(+), 71 deletions(-) diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go index 0eacbb545..a48290aaf 100644 --- a/pkg/core/object/fmt.go +++ b/pkg/core/object/fmt.go @@ -83,12 +83,13 @@ func NewFormatValidator(opts ...FormatValidatorOption) *FormatValidator { // Validate validates object format. // // Does not validate payload checksum and content. +// If unprepared is true, only fields set by user are validated. // // Returns nil error if object has valid structure. -func (v *FormatValidator) Validate(obj *object.Object) error { +func (v *FormatValidator) Validate(obj *object.Object, unprepared bool) error { if obj == nil { return errNilObject - } else if obj.ID() == nil { + } else if !unprepared && obj.ID() == nil { return errNilID } else if obj.ContainerID() == nil { return errNilCID @@ -102,20 +103,23 @@ func (v *FormatValidator) Validate(obj *object.Object) error { return fmt.Errorf("invalid attributes: %w", err) } - if err := v.validateSignatureKey(obj); err != nil { - return fmt.Errorf("(%T) could not validate signature key: %w", v, err) - } + if !unprepared { + if err := v.validateSignatureKey(obj); err != nil { + return fmt.Errorf("(%T) could not validate signature key: %w", v, err) + } - if err := v.checkExpiration(obj); err != nil { - return fmt.Errorf("object did not pass expiration check: %w", err) - } + if err := v.checkExpiration(obj); err != nil { + return fmt.Errorf("object did not pass expiration check: %w", err) + } - if err := object.CheckHeaderVerificationFields(obj); err != nil { - return fmt.Errorf("(%T) could not validate header fields: %w", v, err) + if err := object.CheckHeaderVerificationFields(obj); err != nil { + return fmt.Errorf("(%T) could not validate header fields: %w", v, err) + } } if obj = obj.Parent(); obj != nil { - return v.Validate(obj) + // Parent object already exists. + return v.Validate(obj, false) } return nil diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go index b64a0ecbf..548130e10 100644 --- a/pkg/core/object/fmt_test.go +++ b/pkg/core/object/fmt_test.go @@ -64,20 +64,20 @@ func TestFormatValidator_Validate(t *testing.T) { require.NoError(t, err) t.Run("nil input", func(t *testing.T) { - require.Error(t, v.Validate(nil)) + require.Error(t, v.Validate(nil, true)) }) t.Run("nil identifier", func(t *testing.T) { obj := object.New() - require.True(t, errors.Is(v.Validate(obj), errNilID)) + require.True(t, errors.Is(v.Validate(obj, false), errNilID)) }) t.Run("nil container identifier", func(t *testing.T) { obj := object.New() obj.SetID(testObjectID(t)) - require.True(t, errors.Is(v.Validate(obj), errNilCID)) + require.True(t, errors.Is(v.Validate(obj, true), errNilCID)) }) t.Run("unsigned object", func(t *testing.T) { @@ -85,7 +85,7 @@ func TestFormatValidator_Validate(t *testing.T) { obj.SetContainerID(cidtest.ID()) obj.SetID(testObjectID(t)) - require.Error(t, v.Validate(obj)) + require.Error(t, v.Validate(obj, true)) }) t.Run("correct w/ session token", func(t *testing.T) { @@ -101,7 +101,7 @@ func TestFormatValidator_Validate(t *testing.T) { require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj)) - require.NoError(t, v.Validate(obj)) + require.NoError(t, v.Validate(obj, false)) }) t.Run("correct w/o session token", func(t *testing.T) { @@ -109,7 +109,7 @@ func TestFormatValidator_Validate(t *testing.T) { require.NoError(t, object.SetIDWithSignature(&ownerKey.PrivateKey, obj)) - require.NoError(t, v.Validate(obj)) + require.NoError(t, v.Validate(obj, false)) }) t.Run("tombstone content", func(t *testing.T) { @@ -197,19 +197,19 @@ func TestFormatValidator_Validate(t *testing.T) { t.Run("invalid attribute value", func(t *testing.T) { val := "text" - err := v.Validate(fn(val)) + err := v.Validate(fn(val), false) require.Error(t, err) }) t.Run("expired object", func(t *testing.T) { val := strconv.FormatUint(curEpoch-1, 10) - err := v.Validate(fn(val)) + err := v.Validate(fn(val), false) require.True(t, errors.Is(err, errExpired)) }) t.Run("alive object", func(t *testing.T) { val := strconv.FormatUint(curEpoch, 10) - err := v.Validate(fn(val)) + err := v.Validate(fn(val), true) require.NoError(t, err) }) }) diff --git a/pkg/services/object/put/streamer.go b/pkg/services/object/put/streamer.go index b6d33a277..0b6764dbe 100644 --- a/pkg/services/object/put/streamer.go +++ b/pkg/services/object/put/streamer.go @@ -94,18 +94,21 @@ func (p *Streamer) initTarget(prm *PutInitPrm) error { if sToken == nil && !prm.hdr.OwnerID().Equal(owner.NewIDFromPublicKey(&sessionKey.PublicKey)) { return fmt.Errorf("(%T) session token is missing but object owner id is different from the default key", p) } - - p.target = transformer.NewPayloadSizeLimiter( - p.maxPayloadSz, - func() transformer.ObjectTarget { - return transformer.NewFormatTarget(&transformer.FormatterParams{ - Key: sessionKey, - NextTarget: p.newCommonTarget(prm), - SessionToken: sToken, - NetworkState: p.networkState, - }) - }, - ) + p.target = &validatingTarget{ + fmt: p.fmtValidator, + unpreparedObject: true, + nextTarget: transformer.NewPayloadSizeLimiter( + p.maxPayloadSz, + func() transformer.ObjectTarget { + return transformer.NewFormatTarget(&transformer.FormatterParams{ + Key: sessionKey, + NextTarget: p.newCommonTarget(prm), + SessionToken: sToken, + NetworkState: p.networkState, + }) + }, + ), + } return nil } diff --git a/pkg/services/object/put/validation.go b/pkg/services/object/put/validation.go index e1470b22b..b338fb83b 100644 --- a/pkg/services/object/put/validation.go +++ b/pkg/services/object/put/validation.go @@ -14,11 +14,14 @@ import ( "github.com/nspcc-dev/tzhash/tz" ) +// validatingTarget validates object format and content. type validatingTarget struct { nextTarget transformer.ObjectTarget fmt *object.FormatValidator + unpreparedObject bool + hash hash.Hash checksum []byte @@ -40,29 +43,31 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error { t.payloadSz = obj.PayloadSize() chunkLn := uint64(len(obj.Payload())) - // check chunk size - if chunkLn > t.payloadSz { - return ErrWrongPayloadSize + if !t.unpreparedObject { + // check chunk size + if chunkLn > t.payloadSz { + return ErrWrongPayloadSize + } + + // check payload size limit + if t.payloadSz > t.maxPayloadSz { + return ErrExceedingMaxSize + } + + cs := obj.PayloadChecksum() + switch typ := cs.Type(); typ { + default: + return fmt.Errorf("(%T) unsupported payload checksum type %v", t, typ) + case checksum.SHA256: + t.hash = sha256.New() + case checksum.TZ: + t.hash = tz.New() + } + + t.checksum = cs.Sum() } - // check payload size limit - if t.payloadSz > t.maxPayloadSz { - return ErrExceedingMaxSize - } - - cs := obj.PayloadChecksum() - switch typ := cs.Type(); typ { - default: - return fmt.Errorf("(%T) unsupported payload checksum type %v", t, typ) - case checksum.SHA256: - t.hash = sha256.New() - case checksum.TZ: - t.hash = tz.New() - } - - t.checksum = cs.Sum() - - if err := t.fmt.Validate(obj); err != nil { + if err := t.fmt.Validate(obj, t.unpreparedObject); err != nil { return fmt.Errorf("(%T) coult not validate object format: %w", t, err) } @@ -71,11 +76,13 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error { return err } - // update written bytes - // - // Note: we MUST NOT add obj.PayloadSize() since obj - // can carry only the chunk of the full payload - t.writtenPayload += chunkLn + if !t.unpreparedObject { + // update written bytes + // + // Note: we MUST NOT add obj.PayloadSize() since obj + // can carry only the chunk of the full payload + t.writtenPayload += chunkLn + } return nil } @@ -83,14 +90,16 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error { func (t *validatingTarget) Write(p []byte) (n int, err error) { chunkLn := uint64(len(p)) - // check if new chunk will overflow payload size - if t.writtenPayload+chunkLn > t.payloadSz { - return 0, ErrWrongPayloadSize - } + if !t.unpreparedObject { + // check if new chunk will overflow payload size + if t.writtenPayload+chunkLn > t.payloadSz { + return 0, ErrWrongPayloadSize + } - _, err = t.hash.Write(p) - if err != nil { - return + _, err = t.hash.Write(p) + if err != nil { + return + } } n, err = t.nextTarget.Write(p) @@ -102,13 +111,15 @@ func (t *validatingTarget) Write(p []byte) (n int, err error) { } func (t *validatingTarget) Close() (*transformer.AccessIdentifiers, error) { - // check payload size correctness - if t.payloadSz != t.writtenPayload { - return nil, ErrWrongPayloadSize - } + if !t.unpreparedObject { + // check payload size correctness + if t.payloadSz != t.writtenPayload { + return nil, ErrWrongPayloadSize + } - if !bytes.Equal(t.hash.Sum(nil), t.checksum) { - return nil, fmt.Errorf("(%T) incorrect payload checksum", t) + if !bytes.Equal(t.hash.Sum(nil), t.checksum) { + return nil, fmt.Errorf("(%T) incorrect payload checksum", t) + } } return t.nextTarget.Close()