package tree import ( "crypto/ecdsa" "crypto/elliptic" "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/bearer" cidSDK "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/eacl" "github.com/nspcc-dev/neofs-sdk-go/user" ) type message interface { SignedDataSize() int ReadSignedData([]byte) ([]byte, error) GetSignature() *Signature SetSignature(*Signature) } // verifyClient verifies that the request for a client operation was either signed by owner // or contains a valid bearer token. func (s *Service) verifyClient(req message, cid cidSDK.ID, rawBearer []byte, op eacl.Operation) error { err := verifyMessage(req) if err != nil { return err } cnr, err := s.cnrSource.Get(cid) if err != nil { return fmt.Errorf("can't get container %s: %w", cid, err) } ownerID := cnr.Value.Owner() if len(rawBearer) == 0 { // must be signed by the owner // No error is expected because `VerifyDataWithSource` checks the signature. // However, we may use different algorithms in the future, thus this check. pub, err := keys.NewPublicKeyFromBytes(req.GetSignature().GetKey(), elliptic.P256()) if err != nil { return fmt.Errorf("invalid public key: %w", err) } var actualID user.ID user.IDFromKey(&actualID, (ecdsa.PublicKey)(*pub)) if !actualID.Equals(ownerID) { return errors.New("request must be signed by a container owner") } return nil } var bt bearer.Token if err := bt.Unmarshal(rawBearer); err != nil { return fmt.Errorf("invalid bearer token: %w", err) } if !bearer.ResolveIssuer(bt).Equals(ownerID) { return errors.New("bearer token must be signed by the container owner") } if !bt.AssertContainer(cid) { return errors.New("bearer token is created for another container") } if !bt.VerifySignature() { return errors.New("invalid bearer token signature") } tb := bt.EACLTable() // The default action should be DENY, so we use RoleOthers to allow token issuer // to restrict everyone not affected by the previous rules. // This can be simplified after nspcc-dev/neofs-sdk-go#243 . action, found := eacl.NewValidator().CalculateAction(new(eacl.ValidationUnit). WithEACLTable(&tb). WithContainerID(&cid). WithRole(eacl.RoleOthers). WithSenderKey(req.GetSignature().GetKey()). WithOperation(op)) if !found || action != eacl.ActionAllow { return errors.New("operation denied by bearer eACL") } return nil } func verifyMessage(m message) error { binBody, err := m.ReadSignedData(nil) if err != nil { return fmt.Errorf("marshal request body: %w", err) } sig := m.GetSignature() // TODO(@cthulhu-rider): #1387 use Signature message from NeoFS API to avoid conversion var sigV2 refs.Signature sigV2.SetKey(sig.GetKey()) sigV2.SetSign(sig.GetSign()) sigV2.SetScheme(refs.ECDSA_SHA512) var sigSDK neofscrypto.Signature if err := sigSDK.ReadFromV2(sigV2); err != nil { return fmt.Errorf("can't read signature: %w", err) } if !sigSDK.Verify(binBody) { return errors.New("invalid signature") } return nil } func signMessage(m message, key *ecdsa.PrivateKey) error { binBody, err := m.ReadSignedData(nil) if err != nil { return err } keySDK := neofsecdsa.Signer(*key) data, err := keySDK.Sign(binBody) if err != nil { return err } rawPub := make([]byte, keySDK.Public().MaxEncodedSize()) rawPub = rawPub[:keySDK.Public().Encode(rawPub)] m.SetSignature(&Signature{ Key: rawPub, Sign: data, }) return nil }