package list import ( "bytes" "errors" "fmt" "hash/fnv" "github.com/anytypeio/any-sync/app/logger" "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto" "github.com/anytypeio/any-sync/commonspace/object/keychain" "github.com/anytypeio/any-sync/util/crypto" "github.com/anytypeio/any-sync/util/keys" "github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey" "github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey" "github.com/gogo/protobuf/proto" "go.uber.org/zap" ) var log = logger.NewNamedSugared("common.commonspace.acllist") var ( ErrNoSuchUser = errors.New("no such user") ErrFailedToDecrypt = errors.New("failed to decrypt key") ErrUserRemoved = errors.New("user was removed from the document") ErrDocumentForbidden = errors.New("your user was forbidden access to the document") ErrUserAlreadyExists = errors.New("user already exists") ErrNoSuchRecord = errors.New("no such record") ErrNoSuchInvite = errors.New("no such invite") ErrOldInvite = errors.New("invite is too old") ErrInsufficientPermissions = errors.New("insufficient permissions") ErrNoReadKey = errors.New("acl state doesn't have a read key") ErrInvalidSignature = errors.New("signature is invalid") ErrIncorrectRoot = errors.New("incorrect root") ErrIncorrectRecordSequence = errors.New("incorrect prev id of a record") ) type UserPermissionPair struct { Identity string Permission aclrecordproto.AclUserPermissions } type AclState struct { id string currentReadKeyHash uint64 userReadKeys map[uint64]*crypto.AESKey userStates map[string]*aclrecordproto.AclUserState userInvites map[string]*aclrecordproto.AclUserInvite encryptionKey encryptionkey.PrivKey signingKey signingkey.PrivKey totalReadKeys int identity string permissionsAtRecord map[string][]UserPermissionPair lastRecordId string keychain *keychain.Keychain } func newAclStateWithKeys( id string, signingKey signingkey.PrivKey, encryptionKey encryptionkey.PrivKey) (*AclState, error) { identity, err := signingKey.GetPublic().Raw() if err != nil { return nil, err } return &AclState{ id: id, identity: string(identity), signingKey: signingKey, encryptionKey: encryptionKey, userReadKeys: make(map[uint64]*crypto.AESKey), userStates: make(map[string]*aclrecordproto.AclUserState), userInvites: make(map[string]*aclrecordproto.AclUserInvite), permissionsAtRecord: make(map[string][]UserPermissionPair), }, nil } func newAclState(id string) *AclState { return &AclState{ id: id, userReadKeys: make(map[uint64]*crypto.AESKey), userStates: make(map[string]*aclrecordproto.AclUserState), userInvites: make(map[string]*aclrecordproto.AclUserInvite), permissionsAtRecord: make(map[string][]UserPermissionPair), } } func (st *AclState) CurrentReadKeyHash() uint64 { return st.currentReadKeyHash } func (st *AclState) CurrentReadKey() (*crypto.AESKey, error) { key, exists := st.userReadKeys[st.currentReadKeyHash] if !exists { return nil, ErrNoReadKey } return key, nil } func (st *AclState) UserReadKeys() map[uint64]*crypto.AESKey { return st.userReadKeys } func (st *AclState) PermissionsAtRecord(id string, identity string) (UserPermissionPair, error) { permissions, ok := st.permissionsAtRecord[id] if !ok { log.Errorf("missing record at id %s", id) return UserPermissionPair{}, ErrNoSuchRecord } for _, perm := range permissions { if perm.Identity == identity { return perm, nil } } return UserPermissionPair{}, ErrNoSuchUser } func (st *AclState) applyRecord(record *AclRecord) (err error) { defer func() { if err == nil { st.lastRecordId = record.Id } }() if st.lastRecordId != record.PrevId { err = ErrIncorrectRecordSequence return } if record.Id == st.id { root, ok := record.Model.(*aclrecordproto.AclRoot) if !ok { return ErrIncorrectRoot } err = st.applyRoot(root) if err != nil { return } st.permissionsAtRecord[record.Id] = []UserPermissionPair{ {Identity: string(root.Identity), Permission: aclrecordproto.AclUserPermissions_Admin}, } return } aclData := &aclrecordproto.AclData{} if record.Model != nil { aclData = record.Model.(*aclrecordproto.AclData) } else { err = proto.Unmarshal(record.Data, aclData) if err != nil { return } record.Model = aclData } err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity) if err != nil { return } // getting all permissions for users at record var permissions []UserPermissionPair for _, state := range st.userStates { permission := UserPermissionPair{ Identity: string(state.Identity), Permission: state.Permissions, } permissions = append(permissions, permission) } st.permissionsAtRecord[record.Id] = permissions return } func (st *AclState) applyRoot(root *aclrecordproto.AclRoot) (err error) { if st.signingKey != nil && st.encryptionKey != nil && st.identity == string(root.Identity) { err = st.saveReadKeyFromRoot(root) if err != nil { return } } // adding user to the list userState := &aclrecordproto.AclUserState{ Identity: root.Identity, EncryptionKey: root.EncryptionKey, Permissions: aclrecordproto.AclUserPermissions_Admin, } st.currentReadKeyHash = root.CurrentReadKeyHash st.userStates[string(root.Identity)] = userState st.totalReadKeys++ return } func (st *AclState) saveReadKeyFromRoot(root *aclrecordproto.AclRoot) (err error) { var readKey *crypto.AESKey if len(root.GetDerivationScheme()) != 0 { var encPrivKey []byte encPrivKey, err = st.encryptionKey.Raw() if err != nil { return } var signPrivKey []byte signPrivKey, err = st.signingKey.Raw() if err != nil { return } readKey, err = aclrecordproto.AclReadKeyDerive(signPrivKey, encPrivKey) if err != nil { return } } else { readKey, _, err = st.decryptReadKeyAndHash(root.EncryptedReadKey) if err != nil { return } } hasher := fnv.New64() _, err = hasher.Write(readKey.Bytes()) if err != nil { return } if hasher.Sum64() != root.CurrentReadKeyHash { return ErrIncorrectRoot } st.userReadKeys[root.CurrentReadKeyHash] = readKey return } func (st *AclState) applyChangeData(changeData *aclrecordproto.AclData, hash uint64, identity []byte) (err error) { defer func() { if err != nil { return } if hash != st.currentReadKeyHash { st.totalReadKeys++ st.currentReadKeyHash = hash } }() if !st.isUserJoin(changeData) { // we check signature when we add this to the List, so no need to do it here if _, exists := st.userStates[string(identity)]; !exists { err = ErrNoSuchUser return } if !st.HasPermission(identity, aclrecordproto.AclUserPermissions_Admin) { err = fmt.Errorf("user %s must have admin permissions", identity) return } } for _, ch := range changeData.GetAclContent() { if err = st.applyChangeContent(ch); err != nil { log.Info("error while applying changes: %v; ignore", zap.Error(err)) return err } } return nil } func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue) error { switch { case ch.GetUserPermissionChange() != nil: return st.applyUserPermissionChange(ch.GetUserPermissionChange()) case ch.GetUserAdd() != nil: return st.applyUserAdd(ch.GetUserAdd()) case ch.GetUserRemove() != nil: return st.applyUserRemove(ch.GetUserRemove()) case ch.GetUserInvite() != nil: return st.applyUserInvite(ch.GetUserInvite()) case ch.GetUserJoin() != nil: return st.applyUserJoin(ch.GetUserJoin()) default: return fmt.Errorf("unexpected change type: %v", ch) } } func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange) error { chIdentity := string(ch.Identity) state, exists := st.userStates[chIdentity] if !exists { return ErrNoSuchUser } state.Permissions = ch.Permissions return nil } func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite) error { st.userInvites[string(ch.AcceptPublicKey)] = ch return nil } func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin) error { invite, exists := st.userInvites[string(ch.AcceptPubKey)] if !exists { return fmt.Errorf("no such invite with such public key %s", keys.EncodeBytesToString(ch.AcceptPubKey)) } chIdentity := string(ch.Identity) if _, exists = st.userStates[chIdentity]; exists { return ErrUserAlreadyExists } // validating signature signature := ch.GetAcceptSignature() verificationKey, err := crypto.NewSigningEd25519PubKeyFromBytes(invite.AcceptPublicKey) if err != nil { return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err) } res, err := verificationKey.Verify(ch.Identity, signature) if err != nil { return fmt.Errorf("verification returned error: %w", err) } if !res { return ErrInvalidSignature } // if ourselves -> we need to decrypt the read keys if st.identity == chIdentity { for _, key := range ch.EncryptedReadKeys { key, hash, err := st.decryptReadKeyAndHash(key) if err != nil { return ErrFailedToDecrypt } st.userReadKeys[hash] = key } } // adding user to the list userState := &aclrecordproto.AclUserState{ Identity: ch.Identity, EncryptionKey: ch.EncryptionKey, Permissions: invite.Permissions, } st.userStates[chIdentity] = userState return nil } func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd) error { chIdentity := string(ch.Identity) if _, exists := st.userStates[chIdentity]; exists { return ErrUserAlreadyExists } st.userStates[chIdentity] = &aclrecordproto.AclUserState{ Identity: ch.Identity, EncryptionKey: ch.EncryptionKey, Permissions: ch.Permissions, } if chIdentity == st.identity { for _, key := range ch.EncryptedReadKeys { key, hash, err := st.decryptReadKeyAndHash(key) if err != nil { return ErrFailedToDecrypt } st.userReadKeys[hash] = key } } return nil } func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove) error { chIdentity := string(ch.Identity) if chIdentity == st.identity { return ErrDocumentForbidden } if _, exists := st.userStates[chIdentity]; !exists { return ErrNoSuchUser } delete(st.userStates, chIdentity) for _, replace := range ch.ReadKeyReplaces { repIdentity := string(replace.Identity) // if this is our identity then we have to decrypt the key if repIdentity == st.identity { key, hash, err := st.decryptReadKeyAndHash(replace.EncryptedReadKey) if err != nil { return ErrFailedToDecrypt } st.userReadKeys[hash] = key break } } return nil } func (st *AclState) decryptReadKeyAndHash(msg []byte) (*crypto.AESKey, uint64, error) { decrypted, err := st.encryptionKey.Decrypt(msg) if err != nil { return nil, 0, ErrFailedToDecrypt } key, err := crypto.UnmarshallAESKey(decrypted) if err != nil { return nil, 0, ErrFailedToDecrypt } hasher := fnv.New64() hasher.Write(decrypted) return key, hasher.Sum64(), nil } func (st *AclState) HasPermission(identity []byte, permission aclrecordproto.AclUserPermissions) bool { state, exists := st.userStates[string(identity)] if !exists { return false } return state.Permissions == permission } func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool { // if we have a UserJoin, then it should always be the first one applied return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil } func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool { // if we have a UserAdd, then it should always be the first one applied userAdd := data.GetAclContent()[0].GetUserAdd() return data.GetAclContent() != nil && userAdd != nil && bytes.Compare(userAdd.GetIdentity(), identity) == 0 } func (st *AclState) UserStates() map[string]*aclrecordproto.AclUserState { return st.userStates } func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) { invite, exists := st.userInvites[string(acceptPubKey)] if !exists { err = ErrNoSuchInvite return } if len(invite.EncryptedReadKeys) != st.totalReadKeys { err = ErrOldInvite } return } func (st *AclState) UserKeys() (encKey encryptionkey.PrivKey, signKey signingkey.PrivKey) { return st.encryptionKey, st.signingKey } func (st *AclState) Identity() []byte { return []byte(st.identity) } func (st *AclState) LastRecordId() string { return st.lastRecordId }