From 847b04e4fc21150892dc3d84bfa1958706f5a7d0 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Fri, 19 Nov 2021 12:27:52 -0800 Subject: [PATCH] More comments + UpdateMessageAttribute public API --- model/group.go | 44 +++++++++++++++++++++++ model/profile.go | 51 +++----------------------- peer/cwtch_peer.go | 76 +++++++-------------------------------- peer/profile_interface.go | 7 +--- 4 files changed, 62 insertions(+), 116 deletions(-) diff --git a/model/group.go b/model/group.go index 2356404..8e1d2d9 100644 --- a/model/group.go +++ b/model/group.go @@ -11,12 +11,14 @@ import ( "encoding/json" "errors" "fmt" + "git.openprivacy.ca/cwtch.im/tapir/primitives" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" "golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/pbkdf2" "io" "strings" + "time" ) // CurrentGroupVersion is used to set the version of newly created groups and make sure group structs stored are correct and up to date @@ -229,3 +231,45 @@ func (g *Group) VerifyGroupMessage(onion string, groupID string, message string, } return false } + +// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and +// profile +func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) { + if len(message) > MaxGroupMessageLength { + return nil, nil, nil, errors.New("group message is too long") + } + timestamp := time.Now().Unix() + + // Select the latest message from the timeline as a reference point. + var prevSig []byte + if len(group.Timeline.Messages) > 0 { + prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature + } else { + prevSig = []byte(group.GroupID) + } + + lenPadding := MaxGroupMessageLength - len(message) + padding := make([]byte, lenPadding) + getRandomness(&padding) + hexGroupID, err := hex.DecodeString(group.GroupID) + if err != nil { + return nil, nil, nil, err + } + + dm := &groups.DecryptedGroupMessage{ + Onion: author.Hostname(), + Text: message, + SignedGroupID: hexGroupID, + Timestamp: uint64(timestamp), + PreviousMessageSig: prevSig, + Padding: padding[:], + } + + ciphertext, err := group.EncryptMessage(dm) + if err != nil { + return nil, nil, nil, err + } + serialized, _ := json.Marshal(dm) + signature := author.Sign([]byte(group.GroupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized))) + return ciphertext, signature, dm, nil +} diff --git a/model/profile.go b/model/profile.go index b788080..c18239f 100644 --- a/model/profile.go +++ b/model/profile.go @@ -2,20 +2,17 @@ package model import ( "crypto/rand" - "cwtch.im/cwtch/protocol/groups" - "encoding/base64" "encoding/hex" "encoding/json" - "errors" - "git.openprivacy.ca/cwtch.im/tapir/primitives" "golang.org/x/crypto/ed25519" "io" "path/filepath" "sync" - "time" ) // Authorization is a type determining client assigned authorization to a peer +// Deprecated - Only used for Importing legacy profile formats +// Still used in some APIs in UI but will be replaced prior to full deprecation type Authorization string const ( @@ -28,6 +25,7 @@ const ( ) // PublicProfile is a local copy of a CwtchIdentity +// Deprecated - Only used for Importing legacy profile formats type PublicProfile struct { Name string Ed25519PublicKey ed25519.PublicKey @@ -43,6 +41,7 @@ type PublicProfile struct { } // Profile encapsulates all the attributes necessary to be a Cwtch Peer. +// Deprecated - Only used for Importing legacy profile formats type Profile struct { PublicProfile Contacts map[string]*PublicProfile @@ -71,48 +70,6 @@ func GenerateRandomID() string { return filepath.Join(hex.EncodeToString(randBytes)) } -// EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and -// profile -func EncryptMessageToGroup(message string, author primitives.Identity, group *Group) ([]byte, []byte, *groups.DecryptedGroupMessage, error) { - if len(message) > MaxGroupMessageLength { - return nil, nil, nil, errors.New("group message is too long") - } - timestamp := time.Now().Unix() - - // Select the latest message from the timeline as a reference point. - var prevSig []byte - if len(group.Timeline.Messages) > 0 { - prevSig = group.Timeline.Messages[len(group.Timeline.Messages)-1].Signature - } else { - prevSig = []byte(group.GroupID) - } - - lenPadding := MaxGroupMessageLength - len(message) - padding := make([]byte, lenPadding) - getRandomness(&padding) - hexGroupID, err := hex.DecodeString(group.GroupID) - if err != nil { - return nil, nil, nil, err - } - - dm := &groups.DecryptedGroupMessage{ - Onion: author.Hostname(), - Text: message, - SignedGroupID: hexGroupID, - Timestamp: uint64(timestamp), - PreviousMessageSig: prevSig, - Padding: padding[:], - } - - ciphertext, err := group.EncryptMessage(dm) - if err != nil { - return nil, nil, nil, err - } - serialized, _ := json.Marshal(dm) - signature := author.Sign([]byte(group.GroupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized))) - return ciphertext, signature, dm, nil -} - // GetCopy returns a full deep copy of the Profile struct and its members (timeline inclusion control by arg) func (p *Profile) GetCopy(timeline bool) *Profile { p.lock.Lock() diff --git a/peer/cwtch_peer.go b/peer/cwtch_peer.go index e1dd451..ab60044 100644 --- a/peer/cwtch_peer.go +++ b/peer/cwtch_peer.go @@ -223,16 +223,6 @@ func (cp *cwtchPeer) SendMessage(conversation int, message string) error { return fmt.Errorf("error sending message to conversation %v", err) } -// UpdateMessageFlags -// Status: TODO -func (cp *cwtchPeer) UpdateMessageFlags(handle string, mIdx int, flags uint64) { - cp.mutex.Lock() - defer cp.mutex.Unlock() - log.Debugf("Updating Flags for %v %v %v", handle, mIdx, flags) - //cp.Profile.UpdateMessageFlags(handle, mIdx, flags) - cp.eventBus.Publish(event.NewEvent(event.UpdateMessageFlags, map[event.Field]string{event.Handle: handle, event.Index: strconv.Itoa(mIdx), event.Flags: strconv.FormatUint(flags, 2)})) -} - // BlockUnknownConnections will auto disconnect from connections if authentication doesn't resolve a hostname // known to peer. // Status: Ready for 1.5 @@ -385,36 +375,6 @@ func ImportLegacyProfile(profile *model.Profile, cps *CwtchProfileStorage) Cwtch func (cp *cwtchPeer) Init(eventBus event.Manager) { cp.InitForEvents(eventBus, DefaultEventsToHandle) - // Upgrade the Cwtch Peer if necessary - // It would be nice to do these checks in the storage engine itself, but it is easier to do them here - // rather than duplicating the logic to construct/reconstruct attributes in storage engine... - // TODO: Remove these checks after Cwtch ~1.5 storage engine is implemented - // TODO: Move this to import script - //if _, exists := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name); !exists { - // // If public.profile.name does not exist, and we have an existing public.name then: - // // set public.profile.name from public.name - // // set local.profile.name from public.name - // if name, exists := cp.GetAttribute(attr.GetPublicScope(constants.Name)); exists { - // cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name) - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name) - // } else { - // // Otherwise check if local.name exists and set it from that - // // If not, then check the very old unzoned, unscoped name. - // // If not, then set directly from Profile.Name... - // if name, exists := cp.Profile.GetAttribute(attr.GetLocalScope(constants.Name)); exists { - // cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name) - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name) - // } else if name, exists := cp.Profile.GetAttribute(constants.Name); exists { - // cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, name) - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, name) - // } else { - // // Profile.Name is very deprecated at this point... - // cp.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, cp.Profile.Name) - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, cp.Profile.Name) - // } - // } - //} - // At this point we can safely assume that public.profile.name exists localName, _ := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name) publicName, _ := cp.GetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name) @@ -422,21 +382,6 @@ func (cp *cwtchPeer) Init(eventBus event.Manager) { if localName != publicName { cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Name, publicName) } - - // At this point we can safely assume that public.profile.name exists AND is consistent with - // local.profile.name - regardless of whatever Cwtch version we have upgraded from. This will - // be important after Cwtch 1.5 when we purge all previous references to local.profile.name and - // profile-> name - and remove all name processing code from libcwtch-go. - - // If local.profile.tag does not exist then set it from deprecated GetAttribute - //if _, exists := cp.GetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag); !exists { - // if tag, exists := cp.Profile.GetAttribute(constants.Tag); exists { - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, tag) - // } else { - // // Assume a default password, which will allow the older profile to have it's password reset by the UI - // cp.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.Tag, constants.ProfileTypeV1DefaultPassword) - // } - //} } // InitForEvents @@ -584,6 +529,17 @@ func (cp *cwtchPeer) GetMostRecentMessages(conversation int, channel int, offset return cp.storage.GetMostRecentMessages(conversation, channel, offset, limit) } +func (cp *cwtchPeer) UpdateMessageAttribute(conversation int, channel int, id int, key string, value string) error { + _, attr, err := cp.GetChannelMessage(conversation, channel, id) + if err == nil { + cp.mutex.Lock() + defer cp.mutex.Unlock() + attr[key] = value + return cp.storage.UpdateMessageAttributes(conversation, channel, id, attr) + } + return err +} + // StartGroup create a new group linked to the given server and returns the group ID, an invite or an error. // Status: TODO change server handle to conversation id...? func (cp *cwtchPeer) StartGroup(name string, server string) (int, error) { @@ -714,14 +670,8 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa return err } - // groupServer, isGroup := conversationInfo.Attributes[event.GroupServer]; isGroup { - //bundle, _ := cp.Get(profile.GetGroup(target).GroupServer).GetAttribute(string(model.BundleType)) - //inviteStr, err := profile.GetGroup(target).Invite() - //if err == nil { - // invite = model.MessageWrapper{Overlay: 101, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), inviteStr)} - //} if tor.IsValidHostname(inviteConversationInfo.Handle) { - invite = model.MessageWrapper{Overlay: 100, Data: inviteConversationInfo.Handle} + invite = model.MessageWrapper{Overlay: model.OverlayInviteContact, Data: inviteConversationInfo.Handle} } else { // Reconstruct Group groupID, ok := inviteConversationInfo.Attributes[attr.LocalScope.ConstructScopedZonedPath(attr.LegacyGroupZone.ConstructZonedPath(constants.GroupID)).ToString()] @@ -771,7 +721,7 @@ func (cp *cwtchPeer) SendInviteToConversation(conversationID int, inviteConversa return errors.New("server bundle not found") } - invite = model.MessageWrapper{Overlay: 101, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), groupInvite)} + invite = model.MessageWrapper{Overlay: model.OverlayInviteContact, Data: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), groupInvite)} } inviteBytes, err := json.Marshal(invite) diff --git a/peer/profile_interface.go b/peer/profile_interface.go index 433d0be..e6a34d1 100644 --- a/peer/profile_interface.go +++ b/peer/profile_interface.go @@ -51,11 +51,6 @@ type SendMessages interface { SendScopedZonedGetValToContact(conversationID int, scope attr.Scope, zone attr.Zone, key string) } -// ModifyMessages enables a caller to modify the messages in a timeline -type ModifyMessages interface { - UpdateMessageFlags(string, int, uint64) -} - // CwtchPeer provides us with a way of testing systems built on top of cwtch without having to // directly implement a cwtchPeer. type CwtchPeer interface { @@ -95,7 +90,6 @@ type CwtchPeer interface { ModifyServers SendMessages - ModifyMessages // Import Bundle ImportBundle(string) error @@ -116,6 +110,7 @@ type CwtchPeer interface { GetChannelMessageCount(conversation int, channel int) (int, error) GetChannelMessageByContentHash(conversation int, channel int, contenthash string) (int, error) GetMostRecentMessages(conversation int, channel int, offset int, limit int) ([]model.ConversationMessage, error) + UpdateMessageAttribute(conversation int, channel int, id int, key string, value string) error ShareFile(fileKey string, serializedManifest string) CheckPassword(password string) bool