package tree import ( "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "errors" "fmt" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) type message interface { SignedDataSize() int ReadSignedData([]byte) ([]byte, error) GetSignature() *Signature SetSignature(*Signature) } var ( errBearerWrongContainer = errors.New("bearer token is created for another container") errBearerSignature = errors.New("invalid bearer token signature") ) // verifyClient verifies if the request for a client operation // was signed by a key allowed by (e)ACL rules. // Operation must be one of: // - 1. ObjectPut; // - 2. ObjectGet. func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, rawBearer []byte, op acl.Op) error { err := verifyMessage(req) if err != nil { return err } isAuthorized, err := s.isAuthorized(req, op) if isAuthorized || err != nil { return err } cnr, err := s.cnrSource.Get(cid) if err != nil { return fmt.Errorf("can't get container %s: %w", cid, err) } bt, err := parseBearer(rawBearer, cid) if err != nil { return fmt.Errorf("access to operation %s is denied: %w", op, err) } role, pubKey, err := roleAndPubKeyFromReq(cnr, req, bt) if err != nil { return fmt.Errorf("can't get request role: %w", err) } return s.checkAPE(ctx, bt, cnr, cid, op, role, pubKey) } // Returns true iff the operation is read-only and request was signed // with one of the authorized keys. func (s *Service) isAuthorized(req message, op acl.Op) (bool, error) { if op != acl.OpObjectGet { return false, nil } sign := req.GetSignature() if sign == nil { return false, errors.New("missing signature") } key := sign.GetKey() for i := range s.authorizedKeys { if bytes.Equal(s.authorizedKeys[i], key) { return true, nil } } return false, nil } func parseBearer(rawBearer []byte, cid cidSDK.ID) (*bearer.Token, error) { if len(rawBearer) == 0 { return nil, nil } bt := new(bearer.Token) if err := bt.Unmarshal(rawBearer); err != nil { return nil, fmt.Errorf("invalid bearer token: %w", err) } if !bt.AssertContainer(cid) { return nil, errBearerWrongContainer } if !bt.VerifySignature() { return nil, errBearerSignature } return bt, 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): #468 use Signature message from FrostFS API to avoid conversion var sigV2 refs.Signature sigV2.SetKey(sig.GetKey()) sigV2.SetSign(sig.GetSign()) sigV2.SetScheme(refs.ECDSA_SHA512) var sigSDK frostfscrypto.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 } // SignMessage uses the provided key and signs any protobuf // message that was generated for the TreeService by the // protoc-gen-go-frostfs generator. Returns any errors directly. func SignMessage(m message, key *ecdsa.PrivateKey) error { binBody, err := m.ReadSignedData(nil) if err != nil { return err } keySDK := frostfsecdsa.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 } func roleAndPubKeyFromReq(cnr *core.Container, req message, bt *bearer.Token) (acl.Role, *keys.PublicKey, error) { role := acl.RoleOthers owner := cnr.Value.Owner() rawKey := req.GetSignature().GetKey() if bt != nil && bt.Impersonate() { rawKey = bt.SigningKeyBytes() } pub, err := keys.NewPublicKeyFromBytes(rawKey, elliptic.P256()) if err != nil { return role, nil, fmt.Errorf("invalid public key: %w", err) } var reqSigner user.ID user.IDFromKey(&reqSigner, (ecdsa.PublicKey)(*pub)) if reqSigner.Equals(owner) { role = acl.RoleOwner } return role, pub, nil }