From ede033256d5f7caeeb4ff9958253ba294eee3739 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Tue, 29 Sep 2020 15:37:19 +0300 Subject: [PATCH] [#56] core/object: Implement format validator Signed-off-by: Leonard Lyubich --- pkg/core/object/fmt.go | 80 ++++++++++++++++++++++++++++ pkg/core/object/fmt_test.go | 101 ++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 pkg/core/object/fmt.go create mode 100644 pkg/core/object/fmt_test.go diff --git a/pkg/core/object/fmt.go b/pkg/core/object/fmt.go new file mode 100644 index 000000000..c0e8102ed --- /dev/null +++ b/pkg/core/object/fmt.go @@ -0,0 +1,80 @@ +package object + +import ( + "bytes" + + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/pkg/errors" +) + +// FormatValidator represents object format validator. +type FormatValidator struct{} + +var errNilObject = errors.New("object is nil") + +var errNilID = errors.New("missing identifier") + +var errNilCID = errors.New("missing container identifier") + +// NewFormatValidator creates, initializes and returns FormatValidator instance. +func NewFormatValidator() *FormatValidator { + return new(FormatValidator) +} + +// Validate validates object format. +// +// Returns nil error if object has valid structure. +func (v *FormatValidator) Validate(obj *Object) error { + if obj == nil { + return errNilObject + } else if obj.GetID() == nil { + return errNilID + } else if obj.GetContainerID() == nil { + return errNilCID + } + + for ; obj.GetID() != nil; obj = NewFromSDK(obj.GetParent()) { + if err := v.validateSignatureKey(obj); err != nil { + return errors.Wrapf(err, "(%T) could not validate signature key", v) + } + + if err := object.CheckHeaderVerificationFields(obj.SDK()); err != nil { + return errors.Wrapf(err, "(%T) could not validate header fields", v) + } + } + + return nil +} + +func (v *FormatValidator) validateSignatureKey(obj *Object) error { + token := obj.GetSessionToken() + key := obj.GetSignature().GetKey() + + if token == nil || !bytes.Equal(token.SessionKey(), key) { + return v.checkOwnerKey(obj.GetOwnerID(), obj.GetSignature().GetKey()) + } + + // FIXME: perform token verification + + return nil +} + +func (v *FormatValidator) checkOwnerKey(id *owner.ID, key []byte) error { + wallet, err := owner.NEO3WalletFromPublicKey(crypto.UnmarshalPublicKey(key)) + if err != nil { + // TODO: check via NeoFSID + return err + } + + id2 := owner.NewID() + id2.SetNeo3Wallet(wallet) + + // FIXME: implement Equal method + if s1, s2 := id.String(), id2.String(); s1 != s2 { + return errors.Errorf("(%T) different owner identifiers %s/%s", v, s1, s2) + } + + return nil +} diff --git a/pkg/core/object/fmt_test.go b/pkg/core/object/fmt_test.go new file mode 100644 index 000000000..995924371 --- /dev/null +++ b/pkg/core/object/fmt_test.go @@ -0,0 +1,101 @@ +package object + +import ( + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/token" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-node/pkg/util/test" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func testSHA(t *testing.T) [sha256.Size]byte { + cs := [sha256.Size]byte{} + + _, err := rand.Read(cs[:]) + require.NoError(t, err) + + return cs +} + +func testContainerID(t *testing.T) *container.ID { + id := container.NewID() + id.SetSHA256(testSHA(t)) + + return id +} + +func testObjectID(t *testing.T) *object.ID { + id := object.NewID() + id.SetSHA256(testSHA(t)) + + return id +} + +func TestFormatValidator_Validate(t *testing.T) { + v := NewFormatValidator() + + t.Run("nil input", func(t *testing.T) { + require.Error(t, v.Validate(nil)) + }) + + t.Run("nil identifier", func(t *testing.T) { + obj := NewRaw() + + require.True(t, errors.Is(v.Validate(obj.Object()), errNilID)) + }) + + t.Run("nil container identifier", func(t *testing.T) { + obj := NewRaw() + obj.SetID(testObjectID(t)) + + require.True(t, errors.Is(v.Validate(obj.Object()), errNilCID)) + }) + + t.Run("unsigned object", func(t *testing.T) { + obj := NewRaw() + obj.SetContainerID(testContainerID(t)) + obj.SetID(testObjectID(t)) + + require.Error(t, v.Validate(obj.Object())) + }) + + t.Run("correct w/ session token", func(t *testing.T) { + sessionKey := test.DecodeKey(-1) + + tok := token.NewSessionToken() + tok.SetSessionKey(crypto.MarshalPublicKey(&sessionKey.PublicKey)) + + obj := NewRaw() + obj.SetContainerID(testContainerID(t)) + obj.SetSessionToken(tok) + + require.NoError(t, object.SetIDWithSignature(sessionKey, obj.SDK())) + + require.NoError(t, v.Validate(obj.Object())) + }) + + t.Run("correct w/o session token", func(t *testing.T) { + ownerKey := test.DecodeKey(-1) + + wallet, err := owner.NEO3WalletFromPublicKey(&ownerKey.PublicKey) + require.NoError(t, err) + + ownerID := owner.NewID() + ownerID.SetNeo3Wallet(wallet) + + obj := NewRaw() + obj.SetContainerID(testContainerID(t)) + obj.SetOwnerID(ownerID) + + require.NoError(t, object.SetIDWithSignature(ownerKey, obj.SDK())) + + require.NoError(t, v.Validate(obj.Object())) + }) +}