[#1286] services/object: Validate object when created with sesssion token

All fields set by a user should be verified.

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2022-04-04 16:04:18 +03:00 committed by LeL
parent c6a9c5cd8c
commit 5736b834c3
4 changed files with 89 additions and 71 deletions

View file

@ -83,12 +83,13 @@ func NewFormatValidator(opts ...FormatValidatorOption) *FormatValidator {
// Validate validates object format. // Validate validates object format.
// //
// Does not validate payload checksum and content. // 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. // 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 { if obj == nil {
return errNilObject return errNilObject
} else if obj.ID() == nil { } else if !unprepared && obj.ID() == nil {
return errNilID return errNilID
} else if obj.ContainerID() == nil { } else if obj.ContainerID() == nil {
return errNilCID return errNilCID
@ -102,20 +103,23 @@ func (v *FormatValidator) Validate(obj *object.Object) error {
return fmt.Errorf("invalid attributes: %w", err) return fmt.Errorf("invalid attributes: %w", err)
} }
if err := v.validateSignatureKey(obj); err != nil { if !unprepared {
return fmt.Errorf("(%T) could not validate signature key: %w", v, err) 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 { if err := v.checkExpiration(obj); err != nil {
return fmt.Errorf("object did not pass expiration check: %w", err) return fmt.Errorf("object did not pass expiration check: %w", err)
} }
if err := object.CheckHeaderVerificationFields(obj); err != nil { if err := object.CheckHeaderVerificationFields(obj); err != nil {
return fmt.Errorf("(%T) could not validate header fields: %w", v, err) return fmt.Errorf("(%T) could not validate header fields: %w", v, err)
}
} }
if obj = obj.Parent(); obj != nil { if obj = obj.Parent(); obj != nil {
return v.Validate(obj) // Parent object already exists.
return v.Validate(obj, false)
} }
return nil return nil

View file

@ -64,20 +64,20 @@ func TestFormatValidator_Validate(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("nil input", func(t *testing.T) { 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) { t.Run("nil identifier", func(t *testing.T) {
obj := object.New() 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) { t.Run("nil container identifier", func(t *testing.T) {
obj := object.New() obj := object.New()
obj.SetID(testObjectID(t)) 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) { t.Run("unsigned object", func(t *testing.T) {
@ -85,7 +85,7 @@ func TestFormatValidator_Validate(t *testing.T) {
obj.SetContainerID(cidtest.ID()) obj.SetContainerID(cidtest.ID())
obj.SetID(testObjectID(t)) 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) { 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, 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) { 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, 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) { 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) { t.Run("invalid attribute value", func(t *testing.T) {
val := "text" val := "text"
err := v.Validate(fn(val)) err := v.Validate(fn(val), false)
require.Error(t, err) require.Error(t, err)
}) })
t.Run("expired object", func(t *testing.T) { t.Run("expired object", func(t *testing.T) {
val := strconv.FormatUint(curEpoch-1, 10) val := strconv.FormatUint(curEpoch-1, 10)
err := v.Validate(fn(val)) err := v.Validate(fn(val), false)
require.True(t, errors.Is(err, errExpired)) require.True(t, errors.Is(err, errExpired))
}) })
t.Run("alive object", func(t *testing.T) { t.Run("alive object", func(t *testing.T) {
val := strconv.FormatUint(curEpoch, 10) val := strconv.FormatUint(curEpoch, 10)
err := v.Validate(fn(val)) err := v.Validate(fn(val), true)
require.NoError(t, err) require.NoError(t, err)
}) })
}) })

View file

@ -94,18 +94,21 @@ func (p *Streamer) initTarget(prm *PutInitPrm) error {
if sToken == nil && !prm.hdr.OwnerID().Equal(owner.NewIDFromPublicKey(&sessionKey.PublicKey)) { 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) return fmt.Errorf("(%T) session token is missing but object owner id is different from the default key", p)
} }
p.target = &validatingTarget{
p.target = transformer.NewPayloadSizeLimiter( fmt: p.fmtValidator,
p.maxPayloadSz, unpreparedObject: true,
func() transformer.ObjectTarget { nextTarget: transformer.NewPayloadSizeLimiter(
return transformer.NewFormatTarget(&transformer.FormatterParams{ p.maxPayloadSz,
Key: sessionKey, func() transformer.ObjectTarget {
NextTarget: p.newCommonTarget(prm), return transformer.NewFormatTarget(&transformer.FormatterParams{
SessionToken: sToken, Key: sessionKey,
NetworkState: p.networkState, NextTarget: p.newCommonTarget(prm),
}) SessionToken: sToken,
}, NetworkState: p.networkState,
) })
},
),
}
return nil return nil
} }

View file

@ -14,11 +14,14 @@ import (
"github.com/nspcc-dev/tzhash/tz" "github.com/nspcc-dev/tzhash/tz"
) )
// validatingTarget validates object format and content.
type validatingTarget struct { type validatingTarget struct {
nextTarget transformer.ObjectTarget nextTarget transformer.ObjectTarget
fmt *object.FormatValidator fmt *object.FormatValidator
unpreparedObject bool
hash hash.Hash hash hash.Hash
checksum []byte checksum []byte
@ -40,29 +43,31 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error {
t.payloadSz = obj.PayloadSize() t.payloadSz = obj.PayloadSize()
chunkLn := uint64(len(obj.Payload())) chunkLn := uint64(len(obj.Payload()))
// check chunk size if !t.unpreparedObject {
if chunkLn > t.payloadSz { // check chunk size
return ErrWrongPayloadSize 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 err := t.fmt.Validate(obj, t.unpreparedObject); err != nil {
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 {
return fmt.Errorf("(%T) coult not validate object format: %w", t, err) 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 return err
} }
// update written bytes if !t.unpreparedObject {
// // update written bytes
// Note: we MUST NOT add obj.PayloadSize() since obj //
// can carry only the chunk of the full payload // Note: we MUST NOT add obj.PayloadSize() since obj
t.writtenPayload += chunkLn // can carry only the chunk of the full payload
t.writtenPayload += chunkLn
}
return nil return nil
} }
@ -83,14 +90,16 @@ func (t *validatingTarget) WriteHeader(obj *objectSDK.Object) error {
func (t *validatingTarget) Write(p []byte) (n int, err error) { func (t *validatingTarget) Write(p []byte) (n int, err error) {
chunkLn := uint64(len(p)) chunkLn := uint64(len(p))
// check if new chunk will overflow payload size if !t.unpreparedObject {
if t.writtenPayload+chunkLn > t.payloadSz { // check if new chunk will overflow payload size
return 0, ErrWrongPayloadSize if t.writtenPayload+chunkLn > t.payloadSz {
} return 0, ErrWrongPayloadSize
}
_, err = t.hash.Write(p) _, err = t.hash.Write(p)
if err != nil { if err != nil {
return return
}
} }
n, err = t.nextTarget.Write(p) 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) { func (t *validatingTarget) Close() (*transformer.AccessIdentifiers, error) {
// check payload size correctness if !t.unpreparedObject {
if t.payloadSz != t.writtenPayload { // check payload size correctness
return nil, ErrWrongPayloadSize if t.payloadSz != t.writtenPayload {
} return nil, ErrWrongPayloadSize
}
if !bytes.Equal(t.hash.Sum(nil), t.checksum) { if !bytes.Equal(t.hash.Sum(nil), t.checksum) {
return nil, fmt.Errorf("(%T) incorrect payload checksum", t) return nil, fmt.Errorf("(%T) incorrect payload checksum", t)
}
} }
return t.nextTarget.Close() return t.nextTarget.Close()