diff --git a/model/profile.go b/model/profile.go index e7b3c10..ad7fcff 100644 --- a/model/profile.go +++ b/model/profile.go @@ -3,17 +3,13 @@ package model import ( "crypto/rand" "cwtch.im/cwtch/protocol/groups" - "encoding/base32" "encoding/base64" "encoding/hex" "encoding/json" "errors" "git.openprivacy.ca/cwtch.im/tapir/primitives" - "git.openprivacy.ca/openprivacy/connectivity/tor" "golang.org/x/crypto/ed25519" "io" - "path/filepath" - "strings" "sync" "time" ) @@ -57,179 +53,6 @@ type Profile struct { // TODO: Should this be per server? const MaxGroupMessageLength = 1800 -// GenerateRandomID generates a random 16 byte hex id code -func GenerateRandomID() string { - randBytes := make([]byte, 16) - rand.Read(randBytes) - return filepath.Join(hex.EncodeToString(randBytes)) -} - -func (p *PublicProfile) init() { - if p.Attributes == nil { - p.Attributes = make(map[string]string) - } - p.UnacknowledgedMessages = make(map[string]int) - p.LocalID = GenerateRandomID() -} - -// SetAttribute allows applications to store arbitrary configuration info at the profile level. -func (p *PublicProfile) SetAttribute(name string, value string) { - p.lock.Lock() - defer p.lock.Unlock() - p.Attributes[name] = value -} - -// IsServer returns true if the profile is associated with a server. -func (p *PublicProfile) IsServer() (isServer bool) { - _, isServer = p.GetAttribute(string(KeyTypeServerOnion)) - return -} - -// GetAttribute returns the value of a value set with SetCustomAttribute. If no such value has been set exists is set to false. -func (p *PublicProfile) GetAttribute(name string) (value string, exists bool) { - p.lock.Lock() - defer p.lock.Unlock() - value, exists = p.Attributes[name] - return -} - -// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name. -func GenerateNewProfile(name string) *Profile { - p := new(Profile) - p.init() - p.Name = name - pub, priv, _ := ed25519.GenerateKey(rand.Reader) - p.Ed25519PublicKey = pub - p.Ed25519PrivateKey = priv - p.Onion = tor.GetTorV3Hostname(pub) - - p.Contacts = make(map[string]*PublicProfile) - p.Contacts[p.Onion] = &p.PublicProfile - p.Groups = make(map[string]*Group) - return p -} - -// AddContact allows direct manipulation of cwtch contacts -func (p *Profile) AddContact(onion string, profile *PublicProfile) { - p.lock.Lock() - profile.init() - // We expect callers to verify addresses before we get to this point, so if this isn't a - // valid address this is a noop. - if tor.IsValidHostname(onion) { - decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56])) - if err == nil { - profile.Ed25519PublicKey = ed25519.PublicKey(decodedPub[:32]) - p.Contacts[onion] = profile - } - } - p.lock.Unlock() -} - -// AddSentMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile. -func (p *Profile) AddSentMessageToContactTimeline(onion string, messageTxt string, sent time.Time, eventID string) *Message { - p.lock.Lock() - defer p.lock.Unlock() - - contact, ok := p.Contacts[onion] - if ok { - now := time.Now() - sig := p.SignMessage(onion + messageTxt + sent.String() + now.String()) - - message := &Message{PeerID: p.Onion, Message: messageTxt, Timestamp: sent, Received: now, Signature: sig, Acknowledged: false} - if contact.UnacknowledgedMessages == nil { - contact.UnacknowledgedMessages = make(map[string]int) - } - contact.Timeline.Insert(message) - contact.UnacknowledgedMessages[eventID] = contact.Timeline.Len() - 1 - return message - } - return nil -} - -// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile. -func (p *Profile) AddMessageToContactTimeline(onion string, messageTxt string, sent time.Time) (message *Message) { - p.lock.Lock() - defer p.lock.Unlock() - contact, ok := p.Contacts[onion] - - // We don't really need a Signature here, but we use it to maintain order - now := time.Now() - sig := p.SignMessage(onion + messageTxt + sent.String() + now.String()) - if ok { - message = &Message{PeerID: onion, Message: messageTxt, Timestamp: sent, Received: now, Signature: sig, Acknowledged: true} - contact.Timeline.Insert(message) - } - return -} - -// ErrorSentMessageToPeer sets a sent message's error message and removes it from the unacknowledged list -func (p *Profile) ErrorSentMessageToPeer(onion string, eventID string, error string) int { - p.lock.Lock() - defer p.lock.Unlock() - - contact, ok := p.Contacts[onion] - if ok { - mIdx, ok := contact.UnacknowledgedMessages[eventID] - if ok { - contact.Timeline.Messages[mIdx].Error = error - delete(contact.UnacknowledgedMessages, eventID) - return mIdx - } - } - return -1 -} - -// AckSentMessageToPeer sets mesage to a peer as acknowledged -func (p *Profile) AckSentMessageToPeer(onion string, eventID string) int { - p.lock.Lock() - defer p.lock.Unlock() - - contact, ok := p.Contacts[onion] - if ok { - mIdx, ok := contact.UnacknowledgedMessages[eventID] - if ok { - contact.Timeline.Messages[mIdx].Acknowledged = true - delete(contact.UnacknowledgedMessages, eventID) - return mIdx - } - } - - return -1 -} - -// SignMessage takes a given message and returns an Ed21159 signature -func (p *Profile) SignMessage(message string) []byte { - sig := ed25519.Sign(p.Ed25519PrivateKey, []byte(message)) - return sig -} - -//// ProcessInvite validates a group invite and adds a new group invite to the profile if it is valid. -//// returns the new group ID on success, error on fail. -//func (p *Profile) ProcessInvite(invite string) (string, error) { -// gci, err := ValidateInvite(invite) -// if err == nil { -// if server, exists := p.GetContact(gci.ServerHost); !exists || !server.IsServer() { -// return "", fmt.Errorf("unknown server. a server key bundle needs to be imported before this group can be verified") -// } -// group := new(Group) -// group.Version = CurrentGroupVersion -// group.GroupID = gci.GroupID -// group.LocalID = GenerateRandomID() -// copy(group.GroupKey[:], gci.SharedKey[:]) -// group.GroupServer = gci.ServerHost -// group.Accepted = false -// group.Attributes = make(map[string]string) -// group.Attributes[attr.GetLocalScope(constants.Name)] = gci.GroupName -// p.AddGroup(group) -// return gci.GroupID, nil -// } -// return "", err -//} - -// -// - -// func getRandomness(arr *[]byte) { if _, err := io.ReadFull(rand.Reader, (*arr)[:]); err != nil { if err != nil { diff --git a/storage/v1/profile_store.go b/storage/v1/profile_store.go index 30c9808..9488ea8 100644 --- a/storage/v1/profile_store.go +++ b/storage/v1/profile_store.go @@ -103,12 +103,6 @@ func ReadProfile(directory string, key [32]byte, salt [128]byte) (*model.Profile return profile, nil } -// NewProfile creates a new profile for use in the profile store. -func NewProfile(name string) *model.Profile { - profile := model.GenerateNewProfile(name) - return profile -} - // GetNewPeerMessage is for AppService to call on Reload events, to reseed the AppClient with the loaded peers func (ps *ProfileStoreV1) GetNewPeerMessage() *event.Event { message := event.NewEventList(event.NewPeer, event.Identity, ps.profile.LocalID, event.Key, string(ps.key[:]), event.Salt, string(ps.salt[:])) @@ -138,13 +132,7 @@ func (ps *ProfileStoreV1) load() error { } } - // Check if there is any saved history... - saveHistory, keyExists := contact.GetAttribute(event.SaveHistoryKey) - if !keyExists { - contact.SetAttribute(event.SaveHistoryKey, event.DeleteHistoryDefault) - } - - if saveHistory == event.SaveHistoryConfirmed { + if contact.Attributes[event.SaveHistoryKey] == event.SaveHistoryConfirmed { ss := NewStreamStore(ps.directory, contact.LocalID, ps.key) cp.Contacts[contact.Onion].Timeline.SetMessages(ss.Read()) }