frostfs-node/pkg/services/tree/signature.go

133 lines
3.5 KiB
Go

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("`Move` 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
sigSDK.ReadFromV2(sigV2)
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
}