From ead4513feb584a8ba843e5569a213b2bec0d51b1 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 27 May 2021 15:07:39 +0300 Subject: [PATCH] [#525] ir/container: Verify operations with session token Session token can be presented `Put`, `Delete` and `SetEACL` notification events. IR should consider this case as issuing a power of attorney to a third party. Thus, checking the eligibility for an operation should be complicated: - token owner should be the owner of the related container; - the intent must be signed with a session key; - the power of attorney must be signed by the owner of the container. Omitted checks (TBD): - session token should have container session context; - the verb of the context should correspond to the operation. Signed-off-by: Leonard Lyubich --- pkg/innerring/processors/container/common.go | 85 +++++++++++++++++++ .../processors/container/process_container.go | 44 ++++++++-- .../processors/container/process_eacl.go | 19 +++-- 3 files changed, 136 insertions(+), 12 deletions(-) diff --git a/pkg/innerring/processors/container/common.go b/pkg/innerring/processors/container/common.go index e7449fa57..73112a3b6 100644 --- a/pkg/innerring/processors/container/common.go +++ b/pkg/innerring/processors/container/common.go @@ -1,18 +1,51 @@ package container import ( + "bytes" "crypto/ecdsa" + "crypto/elliptic" + "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/session" + "github.com/nspcc-dev/neofs-api-go/util/signature" + signature2 "github.com/nspcc-dev/neofs-api-go/v2/signature" ) type ownerIDSource interface { OwnerID() *owner.ID } +func tokenFromEvent(src interface { + SessionToken() []byte +}) (*session.Token, error) { + binToken := src.SessionToken() + + if len(binToken) == 0 { + return nil, nil + } + + tok := session.NewToken() + + err := tok.Unmarshal(binToken) + if err != nil { + return nil, fmt.Errorf("could not unmarshal session token: %w", err) + } + + return tok, nil +} + func (cp *Processor) checkKeyOwnership(ownerIDSrc ownerIDSource, key *keys.PublicKey) error { + if tokenSrc, ok := ownerIDSrc.(interface { + SessionToken() *session.Token + }); ok { + if token := tokenSrc.SessionToken(); token != nil { + return cp.checkKeyOwnershipWithToken(ownerIDSrc, key, tokenSrc.SessionToken()) + } + } + // TODO: need more convenient way to do this w, err := owner.NEO3WalletFromPublicKey(&ecdsa.PublicKey{ X: key.X, @@ -40,3 +73,55 @@ func (cp *Processor) checkKeyOwnership(ownerIDSrc ownerIDSource, key *keys.Publi return fmt.Errorf("key %s is not tied to the owner of the container", key) } + +func (cp *Processor) checkKeyOwnershipWithToken(ownerIDSrc ownerIDSource, key *keys.PublicKey, token *session.Token) error { + // check session key + if !bytes.Equal( + key.Bytes(), + token.SessionKey(), + ) { + return errors.New("signed with a non-session key") + } + + // check owner + // TODO: need Equal method on owner.ID + if token.OwnerID().String() != ownerIDSrc.OwnerID().String() { + return errors.New("owner differs with token owner") + } + + err := cp.checkSessionToken(token) + if err != nil { + return fmt.Errorf("invalid session token: %w", err) + } + + return nil +} + +func (cp *Processor) checkSessionToken(token *session.Token) error { + // verify signature + + // TODO: need more convenient way to do this + // e.g. provide VerifySignature method from Token + + // FIXME: do all so as not to deepen in the version + tokenV2 := token.ToV2() + + signWrapper := signature2.StableMarshalerWrapper{ + SM: tokenV2.GetBody(), + } + if err := signature.VerifyDataWithSource(signWrapper, func() (key, sig []byte) { + tokenSignature := tokenV2.GetSignature() + return tokenSignature.GetKey(), tokenSignature.GetSign() + }); err != nil { + return errors.New("invalid signature") + } + + // check token owner's key ownership + + key, err := keys.NewPublicKeyFromBytes(token.Signature().Key(), elliptic.P256()) + if err != nil { + return fmt.Errorf("invalid key: %w", err) + } + + return cp.checkKeyOwnership(token, key) +} diff --git a/pkg/innerring/processors/container/process_container.go b/pkg/innerring/processors/container/process_container.go index f8665b79d..41c98fa33 100644 --- a/pkg/innerring/processors/container/process_container.go +++ b/pkg/innerring/processors/container/process_container.go @@ -61,6 +61,16 @@ func (cp *Processor) checkPutContainer(e *containerEvent.Put) error { return fmt.Errorf("incorrect container format: %w", err) } + // unmarshal session token if presented + tok, err := tokenFromEvent(e) + if err != nil { + return err + } + + // TODO: check verb and CID + + cnr.SetSessionToken(tok) + return cp.checkKeyOwnership(cnr, key) } @@ -102,18 +112,42 @@ func (cp *Processor) checkDeleteContainer(e *containerEvent.Delete) error { return fmt.Errorf("could not receive the container: %w", err) } - // receive all owner keys - ownerKeys, err := cp.idClient.AccountKeys(cnr.OwnerID()) + token, err := tokenFromEvent(e) if err != nil { - return fmt.Errorf("could not received owner keys %s: %w", cnr.OwnerID(), err) + return err + } + + var checkKeys keys.PublicKeys + + if token != nil { + key, err := keys.NewPublicKeyFromBytes(token.SessionKey(), elliptic.P256()) + if err != nil { + return fmt.Errorf("invalid session key: %w", err) + } + + // TODO: check verb and container ID + + // check token ownership + err = cp.checkKeyOwnershipWithToken(cnr, key, token) + if err != nil { + return err + } + + checkKeys = keys.PublicKeys{key} + } else { + // receive all owner keys from NeoFS ID contract + checkKeys, err = cp.idClient.AccountKeys(cnr.OwnerID()) + if err != nil { + return fmt.Errorf("could not received owner keys %s: %w", cnr.OwnerID(), err) + } } // verify signature cidHash := sha256.Sum256(cid) sig := e.Signature() - for _, ownerKey := range ownerKeys { - if ownerKey.Verify(sig, cidHash[:]) { + for _, key := range checkKeys { + if key.Verify(sig, cidHash[:]) { return nil } } diff --git a/pkg/innerring/processors/container/process_eacl.go b/pkg/innerring/processors/container/process_eacl.go index 7894d1540..529a47d3c 100644 --- a/pkg/innerring/processors/container/process_eacl.go +++ b/pkg/innerring/processors/container/process_eacl.go @@ -38,22 +38,19 @@ func (cp *Processor) checkSetEACL(e container.SetEACL) error { return fmt.Errorf("invalid key: %w", err) } - table := e.Table() - tableHash := sha256.Sum256(table) + binTable := e.Table() + tableHash := sha256.Sum256(binTable) if !key.Verify(e.Signature(), tableHash[:]) { return errors.New("invalid signature") } // verify the identity of the container owner - return cp.checkEACLOwnership(table, key) -} -func (cp *Processor) checkEACLOwnership(binTable []byte, key *keys.PublicKey) error { // unmarshal table table := eacl.NewTable() - err := table.Unmarshal(binTable) + err = table.Unmarshal(binTable) if err != nil { return fmt.Errorf("invalid binary table: %w", err) } @@ -64,8 +61,16 @@ func (cp *Processor) checkEACLOwnership(binTable []byte, key *keys.PublicKey) er return fmt.Errorf("could not receive the container: %w", err) } + // unmarshal session token if presented + tok, err := tokenFromEvent(e) + if err != nil { + return err + } + + // TODO: check verb and container ID + // check key ownership - return cp.checkKeyOwnership(cnr, key) + return cp.checkKeyOwnershipWithToken(cnr, key, tok) } func (cp *Processor) approveSetEACL(e container.SetEACL) {