More comments + UpdateMessageAttribute public API
continuous-integration/drone/push Build is pending Details
continuous-integration/drone/pr Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2021-11-19 12:27:52 -08:00
parent f1caca3adf
commit 847b04e4fc
4 changed files with 62 additions and 116 deletions

View File

@ -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
}

View File

@ -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()

View File

@ -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)

View File

@ -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