forked from TrueCloudLab/frostfs-node
[#1423] session: Upgrade SDK package
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
dda56f1319
commit
4c8ec20e32
41 changed files with 740 additions and 663 deletions
|
@ -3,184 +3,167 @@ package container
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
var (
|
||||
errWrongSessionContext = errors.New("wrong session context")
|
||||
errWrongSessionVerb = errors.New("wrong token verb")
|
||||
errWrongCID = errors.New("wrong container ID")
|
||||
errWrongSessionVerb = errors.New("wrong token verb")
|
||||
errWrongCID = errors.New("wrong container ID")
|
||||
)
|
||||
|
||||
type ownerIDSource interface {
|
||||
OwnerID() *user.ID
|
||||
type signatureVerificationData struct {
|
||||
ownerContainer user.ID
|
||||
|
||||
verb session.ContainerVerb
|
||||
|
||||
idContainerSet bool
|
||||
idContainer cid.ID
|
||||
|
||||
binTokenSession []byte
|
||||
|
||||
binPublicKey []byte
|
||||
|
||||
signature []byte
|
||||
|
||||
signedData []byte
|
||||
}
|
||||
|
||||
func tokenFromEvent(src interface {
|
||||
SessionToken() []byte
|
||||
}) (*session.Token, error) {
|
||||
binToken := src.SessionToken()
|
||||
// verifySignature is a common method of Container service authentication. Asserts that:
|
||||
// - for trusted parties: session is valid (*) and issued by container owner
|
||||
// - operation data is signed by container owner or trusted party
|
||||
// - operation data signature is correct
|
||||
//
|
||||
// (*) includes:
|
||||
// - session token decodes correctly
|
||||
// - signature is valid
|
||||
// - session issued by the container owner
|
||||
// - v.binPublicKey is a public session key
|
||||
// - session context corresponds to the container and verb in v
|
||||
// - session is "alive"
|
||||
func (cp *Processor) verifySignature(v signatureVerificationData) error {
|
||||
var err error
|
||||
var key neofsecdsa.PublicKeyRFC6979
|
||||
keyProvided := v.binPublicKey != nil
|
||||
withSession := len(v.binTokenSession) > 0
|
||||
|
||||
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())
|
||||
if keyProvided {
|
||||
err = key.Decode(v.binPublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode public key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ownerSrc := ownerIDSrc.OwnerID()
|
||||
if ownerSrc == nil {
|
||||
return errors.New("missing owner")
|
||||
if withSession {
|
||||
var tok session.Container
|
||||
|
||||
err = tok.Unmarshal(v.binTokenSession)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode session token: %w", err)
|
||||
}
|
||||
|
||||
if !tok.VerifySignature() {
|
||||
return errors.New("invalid session token signature")
|
||||
}
|
||||
|
||||
// FIXME(@cthulhu-rider): #1387 check token is signed by container owner, see neofs-sdk-go#233
|
||||
// We'll get container owner's keys which is needed below, so it's worth to cache them
|
||||
|
||||
if keyProvided && !tok.AssertAuthKey(&key) {
|
||||
return errors.New("signed with a non-session key")
|
||||
}
|
||||
|
||||
if !tok.AssertVerb(v.verb) {
|
||||
return errWrongSessionVerb
|
||||
}
|
||||
|
||||
if v.idContainerSet && !tok.AppliedTo(v.idContainer) {
|
||||
return errWrongCID
|
||||
}
|
||||
|
||||
if !session.IssuedBy(tok, v.ownerContainer) {
|
||||
return errors.New("owner differs with token owner")
|
||||
}
|
||||
|
||||
err = cp.checkTokenLifetime(tok)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check session lifetime: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var ownerKey user.ID
|
||||
user.IDFromKey(&ownerKey, (ecdsa.PublicKey)(*key))
|
||||
var verificationKeys []neofscrypto.PublicKey
|
||||
|
||||
if ownerSrc.Equals(ownerKey) {
|
||||
return nil
|
||||
if keyProvided {
|
||||
if withSession {
|
||||
verificationKeys = []neofscrypto.PublicKey{&key}
|
||||
} else {
|
||||
var idFromKey user.ID
|
||||
user.IDFromKey(&idFromKey, (ecdsa.PublicKey)(key))
|
||||
|
||||
if v.ownerContainer.Equals(idFromKey) {
|
||||
verificationKeys = []neofscrypto.PublicKey{&key}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prm := neofsid.AccountKeysPrm{}
|
||||
prm.SetID(ownerIDSrc.OwnerID())
|
||||
if verificationKeys == nil {
|
||||
var prm neofsid.AccountKeysPrm
|
||||
prm.SetID(&v.ownerContainer)
|
||||
|
||||
ownerKeys, err := cp.idClient.AccountKeys(prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not received owner keys %s: %w", ownerIDSrc.OwnerID(), err)
|
||||
ownerKeys, err := cp.idClient.AccountKeys(prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("receive owner keys %s: %w", v.ownerContainer, err)
|
||||
}
|
||||
|
||||
if !keyProvided {
|
||||
verificationKeys = make([]neofscrypto.PublicKey, 0, len(ownerKeys))
|
||||
}
|
||||
|
||||
for i := range ownerKeys {
|
||||
if keyProvided {
|
||||
// TODO(@cthulhu-rider): keys have been decoded in order to encode only, should be optimized by #1387
|
||||
if bytes.Equal(ownerKeys[i].Bytes(), v.binPublicKey) {
|
||||
verificationKeys = []neofscrypto.PublicKey{(*neofsecdsa.PublicKeyRFC6979)(ownerKeys[i])}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
verificationKeys = append(verificationKeys, (*neofsecdsa.PublicKeyRFC6979)(ownerKeys[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ownerKey := range ownerKeys {
|
||||
if ownerKey.Equal(key) {
|
||||
if len(verificationKeys) == 0 {
|
||||
return errors.New("key is not a container owner's key")
|
||||
}
|
||||
|
||||
for i := range verificationKeys {
|
||||
if verificationKeys[i].Verify(v.signedData, v.signature) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("key %s is not tied to the owner of the container", key)
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
ownerToken, ownerSrc := token.OwnerID(), ownerIDSrc.OwnerID()
|
||||
|
||||
// check owner
|
||||
if ownerToken == nil || ownerSrc == nil || !ownerToken.Equals(*ownerSrc) {
|
||||
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
|
||||
if !token.VerifySignature() {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
// check lifetime
|
||||
err := cp.checkTokenLifetime(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check token owner's key ownership
|
||||
|
||||
// FIXME(@cthulhu-rider): #1387 see neofs-sdk-go#233
|
||||
key, err := keys.NewPublicKeyFromBytes(token.ToV2().GetSignature().GetKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid key: %w", err)
|
||||
}
|
||||
|
||||
return cp.checkKeyOwnership(token, key)
|
||||
}
|
||||
|
||||
type verbAssert func(*session.ContainerContext) bool
|
||||
|
||||
func contextWithVerifiedVerb(tok *session.Token, verbAssert verbAssert) (*session.ContainerContext, error) {
|
||||
c := session.GetContainerContext(tok)
|
||||
if c == nil {
|
||||
return nil, errWrongSessionContext
|
||||
}
|
||||
|
||||
if !verbAssert(c) {
|
||||
return nil, errWrongSessionVerb
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func checkTokenContext(tok *session.Token, verbAssert verbAssert) error {
|
||||
_, err := contextWithVerifiedVerb(tok, verbAssert)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkTokenContextWithCID(tok *session.Token, id cid.ID, verbAssert verbAssert) error {
|
||||
c, err := contextWithVerifiedVerb(tok, verbAssert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tokCID := c.Container()
|
||||
if tokCID != nil && !tokCID.Equals(id) {
|
||||
return errWrongCID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *Processor) checkTokenLifetime(token *session.Token) error {
|
||||
func (cp *Processor) checkTokenLifetime(token session.Container) error {
|
||||
curEpoch, err := cp.netState.Epoch()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read current epoch: %w", err)
|
||||
}
|
||||
|
||||
nbf := token.Nbf()
|
||||
if curEpoch < nbf {
|
||||
return fmt.Errorf("token is not valid yet: nbf %d, cur %d", nbf, curEpoch)
|
||||
if token.ExpiredAt(curEpoch) {
|
||||
return fmt.Errorf("token is expired at %d", curEpoch)
|
||||
}
|
||||
|
||||
iat := token.Iat()
|
||||
if curEpoch < iat {
|
||||
return fmt.Errorf("token is issued in future: iat %d, cur %d", iat, curEpoch)
|
||||
}
|
||||
|
||||
exp := token.Exp()
|
||||
if curEpoch >= exp {
|
||||
return fmt.Errorf("token is expired: exp %d, cur %d", exp, curEpoch)
|
||||
if token.InvalidAt(curEpoch) {
|
||||
return fmt.Errorf("token is not valid at %d", curEpoch)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/core/container"
|
||||
cntClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/client/neofsid"
|
||||
morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/event"
|
||||
containerEvent "github.com/nspcc-dev/neofs-node/pkg/morph/event/container"
|
||||
|
@ -62,29 +58,32 @@ func (cp *Processor) processContainerPut(put putEvent) {
|
|||
}
|
||||
|
||||
func (cp *Processor) checkPutContainer(ctx *putContainerContext) error {
|
||||
e := ctx.e
|
||||
binCnr := ctx.e.Container()
|
||||
|
||||
// verify signature
|
||||
key, err := keys.NewPublicKeyFromBytes(e.PublicKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid key: %w", err)
|
||||
}
|
||||
|
||||
binCnr := e.Container()
|
||||
tableHash := sha256.Sum256(binCnr)
|
||||
|
||||
if !key.Verify(e.Signature(), tableHash[:]) {
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
// unmarshal container structure
|
||||
cnr := containerSDK.New()
|
||||
|
||||
err = cnr.Unmarshal(binCnr)
|
||||
err := cnr.Unmarshal(binCnr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid binary container: %w", err)
|
||||
}
|
||||
|
||||
ownerContainer := cnr.OwnerID()
|
||||
if ownerContainer == nil {
|
||||
return errors.New("missing container owner")
|
||||
}
|
||||
|
||||
err = cp.verifySignature(signatureVerificationData{
|
||||
ownerContainer: *ownerContainer,
|
||||
verb: session.VerbContainerPut,
|
||||
binTokenSession: ctx.e.SessionToken(),
|
||||
binPublicKey: ctx.e.PublicKey(),
|
||||
signature: ctx.e.Signature(),
|
||||
signedData: binCnr,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("auth container creation: %w", err)
|
||||
}
|
||||
|
||||
// check owner allowance in the subnetwork
|
||||
err = checkSubnet(cp.subnetClient, cnr)
|
||||
if err != nil {
|
||||
|
@ -103,25 +102,7 @@ func (cp *Processor) checkPutContainer(ctx *putContainerContext) error {
|
|||
return fmt.Errorf("incorrect container format: %w", err)
|
||||
}
|
||||
|
||||
// unmarshal session token if presented
|
||||
tok, err := tokenFromEvent(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tok != nil {
|
||||
// check token context
|
||||
err = checkTokenContext(tok, func(c *session.ContainerContext) bool {
|
||||
return c.IsForPut()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cnr.SetSessionToken(tok)
|
||||
|
||||
return cp.checkKeyOwnership(cnr, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *Processor) approvePutContainer(ctx *putContainerContext) {
|
||||
|
@ -175,70 +156,38 @@ func (cp *Processor) processContainerDelete(delete *containerEvent.Delete) {
|
|||
func (cp *Processor) checkDeleteContainer(e *containerEvent.Delete) error {
|
||||
binCID := e.ContainerID()
|
||||
|
||||
var idCnr cid.ID
|
||||
|
||||
err := idCnr.Decode(binCID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid container ID: %w", err)
|
||||
}
|
||||
|
||||
// receive owner of the related container
|
||||
cnr, err := cp.cnrClient.Get(binCID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not receive the container: %w", err)
|
||||
}
|
||||
|
||||
token, err := tokenFromEvent(e)
|
||||
ownerContainer := cnr.OwnerID()
|
||||
if ownerContainer == nil {
|
||||
return errors.New("missing container owner")
|
||||
}
|
||||
|
||||
err = cp.verifySignature(signatureVerificationData{
|
||||
ownerContainer: *ownerContainer,
|
||||
verb: session.VerbContainerDelete,
|
||||
idContainerSet: true,
|
||||
idContainer: idCnr,
|
||||
binTokenSession: e.SessionToken(),
|
||||
signature: e.Signature(),
|
||||
signedData: binCID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("auth container creation: %w", err)
|
||||
}
|
||||
|
||||
var checkKeys keys.PublicKeys
|
||||
|
||||
if token != nil {
|
||||
// check token context
|
||||
// TODO: #1147 think how to avoid version casts
|
||||
var id cid.ID
|
||||
|
||||
err = id.Decode(binCID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode container ID: %w", err)
|
||||
}
|
||||
|
||||
err = checkTokenContextWithCID(token, id, func(c *session.ContainerContext) bool {
|
||||
return c.IsForDelete()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := keys.NewPublicKeyFromBytes(token.SessionKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid session key: %w", err)
|
||||
}
|
||||
|
||||
// check token ownership
|
||||
err = cp.checkKeyOwnershipWithToken(cnr, key, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkKeys = keys.PublicKeys{key}
|
||||
} else {
|
||||
prm := neofsid.AccountKeysPrm{}
|
||||
prm.SetID(cnr.OwnerID())
|
||||
|
||||
// receive all owner keys from NeoFS ID contract
|
||||
checkKeys, err = cp.idClient.AccountKeys(prm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not received owner keys %s: %w", cnr.OwnerID(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// verify signature
|
||||
cidHash := sha256.Sum256(binCID)
|
||||
sig := e.Signature()
|
||||
|
||||
for _, key := range checkKeys {
|
||||
if key.Verify(sig, cidHash[:]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("signature verification failed on all owner keys ")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *Processor) approveDeleteContainer(e *containerEvent.Delete) {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
cntClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/container"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/morph/event/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
|
@ -33,25 +30,12 @@ func (cp *Processor) processSetEACL(e container.SetEACL) {
|
|||
}
|
||||
|
||||
func (cp *Processor) checkSetEACL(e container.SetEACL) error {
|
||||
// verify signature
|
||||
key, err := keys.NewPublicKeyFromBytes(e.PublicKey(), elliptic.P256())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid key: %w", err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// unmarshal table
|
||||
table := eacl.NewTable()
|
||||
|
||||
err = table.Unmarshal(binTable)
|
||||
err := table.Unmarshal(binTable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid binary table: %w", err)
|
||||
}
|
||||
|
@ -67,30 +51,26 @@ func (cp *Processor) checkSetEACL(e container.SetEACL) error {
|
|||
return fmt.Errorf("could not receive the container: %w", err)
|
||||
}
|
||||
|
||||
// unmarshal session token if presented
|
||||
tok, err := tokenFromEvent(e)
|
||||
ownerContainer := cnr.OwnerID()
|
||||
if ownerContainer == nil {
|
||||
return errors.New("missing container owner")
|
||||
}
|
||||
|
||||
err = cp.verifySignature(signatureVerificationData{
|
||||
ownerContainer: *ownerContainer,
|
||||
verb: session.VerbContainerSetEACL,
|
||||
idContainerSet: true,
|
||||
idContainer: idCnr,
|
||||
binTokenSession: e.SessionToken(),
|
||||
binPublicKey: e.PublicKey(),
|
||||
signature: e.Signature(),
|
||||
signedData: binTable,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("auth eACL table setting: %w", err)
|
||||
}
|
||||
|
||||
if tok != nil {
|
||||
// check token context
|
||||
err = checkTokenContextWithCID(tok, idCnr, func(c *session.ContainerContext) bool {
|
||||
return c.IsForSetEACL()
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// statement below is a little hack, but if we write a token from an event to the container,
|
||||
// checkKeyOwnership method will work just as it should:
|
||||
// * tok == nil => we will check if key is a container owner's key
|
||||
// * tok != nil => we will check if token was signed correctly (context is checked at the statement above)
|
||||
cnr.SetSessionToken(tok)
|
||||
|
||||
// check key ownership
|
||||
return cp.checkKeyOwnership(cnr, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *Processor) approveSetEACL(e container.SetEACL) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue