package model import ( "crypto/rand" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/model/constants" "cwtch.im/cwtch/protocol/groups" "encoding/base32" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "git.openprivacy.ca/openprivacy/connectivity/tor" "golang.org/x/crypto/ed25519" "io" "path/filepath" "strings" "sync" "time" ) // Authorization is a type determining client assigned authorization to a peer type Authorization string const ( // AuthUnknown is an initial state for a new unseen peer AuthUnknown Authorization = "unknown" // AuthApproved means the client has approved the peer, it can send messages to us, perform GetVals, etc AuthApproved Authorization = "approved" // AuthBlocked means the client has blocked the peer, it's messages and connections should be rejected AuthBlocked Authorization = "blocked" ) // PublicProfile is a local copy of a CwtchIdentity type PublicProfile struct { Name string Ed25519PublicKey ed25519.PublicKey Authorization Authorization DeprecatedBlocked bool `json:"Blocked"` Onion string Attributes map[string]string Timeline Timeline `json:"-"` LocalID string // used by storage engine State string `json:"-"` lock sync.Mutex UnacknowledgedMessages map[string]int } // Profile encapsulates all the attributes necessary to be a Cwtch Peer. type Profile struct { PublicProfile Contacts map[string]*PublicProfile Ed25519PrivateKey ed25519.PrivateKey Groups map[string]*Group } // MaxGroupMessageLength is the maximum length of a message posted to a server group. // 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() } // UpdateMessageFlags updates the flags stored with a message func (p *Profile) UpdateMessageFlags(handle string, mIdx int, flags uint64) { p.lock.Lock() defer p.lock.Unlock() if contact, exists := p.Contacts[handle]; exists { if len(contact.Timeline.Messages) > mIdx { contact.Timeline.Messages[mIdx].Flags = flags } } else if group, exists := p.Groups[handle]; exists { if len(group.Timeline.Messages) > mIdx { group.Timeline.Messages[mIdx].Flags = flags } } } // DeleteContact deletes a peer contact func (p *Profile) DeleteContact(onion string) { p.lock.Lock() defer p.lock.Unlock() delete(p.Contacts, onion) } // DeleteGroup deletes a group func (p *Profile) DeleteGroup(groupID string) { p.lock.Lock() defer p.lock.Unlock() delete(p.Groups, groupID) } // RejectInvite rejects and removes a group invite func (p *Profile) RejectInvite(groupID string) { p.lock.Lock() delete(p.Groups, groupID) 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 } // AddGroupSentMessageError searches matching groups for the message by sig and marks it as an error func (p *Profile) AddGroupSentMessageError(groupID string, signature []byte, error string) { p.lock.Lock() defer p.lock.Unlock() group, exists := p.Groups[groupID] if exists { group.ErrorSentMessage(signature, error) } } // AcceptInvite accepts a group invite func (p *Profile) AcceptInvite(groupID string) (err error) { p.lock.Lock() defer p.lock.Unlock() group, ok := p.Groups[groupID] if ok { group.Accepted = true } else { err = errors.New("group does not exist") } return } // GetGroups returns an unordered list of group IDs associated with this profile. func (p *Profile) GetGroups() []string { p.lock.Lock() defer p.lock.Unlock() var keys []string for onion := range p.Groups { keys = append(keys, onion) } return keys } // GetContacts returns an unordered list of contact onions associated with this profile. func (p *Profile) GetContacts() []string { p.lock.Lock() defer p.lock.Unlock() var keys []string for onion := range p.Contacts { if onion != p.Onion { keys = append(keys, onion) } } return keys } // SetContactAuthorization sets the authoirization level of a peer func (p *Profile) SetContactAuthorization(onion string, auth Authorization) (err error) { p.lock.Lock() defer p.lock.Unlock() contact, ok := p.Contacts[onion] if ok { contact.Authorization = auth } else { err = errors.New("peer does not exist") } return } // GetContactAuthorization returns the contact's authorization level func (p *Profile) GetContactAuthorization(onion string) Authorization { p.lock.Lock() defer p.lock.Unlock() contact, ok := p.Contacts[onion] if ok { return contact.Authorization } return AuthUnknown } // ContactsAuthorizations calculates a list of Peers who are at the supplied auth levels func (p *Profile) ContactsAuthorizations(authorizationFilter ...Authorization) map[string]Authorization { authorizations := map[string]Authorization{} for _, contact := range p.GetContacts() { c, _ := p.GetContact(contact) authorizations[c.Onion] = c.Authorization } return authorizations } // GetContact returns a contact if the profile has it func (p *Profile) GetContact(onion string) (*PublicProfile, bool) { p.lock.Lock() defer p.lock.Unlock() contact, ok := p.Contacts[onion] return contact, ok } // VerifyGroupMessage confirms the authenticity of a message given an sender onion, message and signature. // The goal of this function is 2-fold: // 1. We confirm that the sender referenced in the group text is the actual sender of the message (or at least // knows the senders private key) // 2. Secondly, we confirm that the sender sent the message to a particular group id on a specific server (it doesn't // matter if we actually received this message from the server or from a hybrid protocol, all that matters is // that the sender and receivers agree that this message was intended for the group // The 2nd point is important as it prevents an attack documented in the original Cwtch paper (and later at // https://docs.openprivacy.ca/cwtch-security-handbook/groups.html) in which a malicious profile sets up 2 groups // on two different servers with the same key and then forwards messages between them to convince the parties in // each group that they are actually in one big group (with the intent to later censor and/or selectively send messages // to each group). func (p *Profile) VerifyGroupMessage(onion string, groupID string, message string, signature []byte) bool { group := p.GetGroup(groupID) if group == nil { return false } // We use our group id, a known reference server and the ciphertext of the message. m := groupID + group.GroupServer + message // If the message is ostensibly from us then we check it against our public key... if onion == p.Onion { return ed25519.Verify(p.Ed25519PublicKey, []byte(m), signature) } // Otherwise we derive the public key from the sender and check it against that. decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) if err == nil && len(decodedPub) >= 32 { return ed25519.Verify(decodedPub[:32], []byte(m), signature) } return false } // 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 } // StartGroup when given a server, creates a new Group under this profile and returns the group id an a precomputed // invite which can be sent on the wire. func (p *Profile) StartGroup(server string) (groupID string, invite string, err error) { group, err := NewGroup(server) if err != nil { return "", "", err } groupID = group.GroupID invite, err = group.Invite() p.lock.Lock() defer p.lock.Unlock() p.Groups[group.GroupID] = group return } // GetGroup a pointer to a Group by the group Id, returns nil if no group found. func (p *Profile) GetGroup(groupID string) (g *Group) { p.lock.Lock() defer p.lock.Unlock() g = p.Groups[groupID] return } // 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 } // AddGroup is a convenience method for adding a group to a profile. func (p *Profile) AddGroup(group *Group) { p.lock.Lock() defer p.lock.Unlock() _, exists := p.Groups[group.GroupID] if !exists { p.Groups[group.GroupID] = group } } // AttemptDecryption takes a ciphertext and signature and attempts to decrypt it under known groups. // If successful, adds the message to the group's timeline func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (bool, string, *Message, int) { for _, group := range p.Groups { success, dgm := group.DecryptMessage(ciphertext) if success { // Attempt to serialize this message serialized, err := json.Marshal(dgm) // Someone send a message that isn't a valid Decrypted Group Message. Since we require this struct in orer // to verify the message, we simply ignore it. if err != nil { return false, group.GroupID, nil, -1 } // This now requires knowledge of the Sender, the Onion and the Specific Decrypted Group Message (which should only // be derivable from the cryptographic key) which contains many unique elements such as the time and random padding verified := p.VerifyGroupMessage(dgm.Onion, group.GroupID, base64.StdEncoding.EncodeToString(serialized), signature) if !verified { // An earlier version of this protocol mistakenly signed the ciphertext of the message // instead of the serialized decrypted group message. // This has 2 issues: // 1. A server with knowledge of group members public keys AND the Group ID would be able to detect valid messages // 2. It made the metadata-security of a group dependent on keeping the cryptographically derived Group ID secret. // While not awful, it also isn't good. For Version 3 groups only we permit Cwtch to check this older signature // structure in a backwards compatible way for the duration of the Groups Experiment. // TODO: Delete this check when Groups are no long Experimental if group.Version == 3 { verified = p.VerifyGroupMessage(dgm.Onion, group.GroupID, string(ciphertext), signature) } } // So we have a message that has a valid group key, but the signature can't be verified. // The most obvious explanation for this is that the group key has been compromised (or we are in an open group and the server is being malicious) // Either way, someone who has the private key is being detectably bad so we are just going to throw this message away and mark the group as Compromised. if !verified { group.Compromised() return false, group.GroupID, nil, -1 } message, index := group.AddMessage(dgm, signature) return true, group.GroupID, message, index } } // If we couldn't find a group to decrypt the message with we just return false. This is an expected case return false, "", nil, -1 } func getRandomness(arr *[]byte) { if _, err := io.ReadFull(rand.Reader, (*arr)[:]); err != nil { if err != nil { // If we can't do randomness, just crash something is very very wrong and we are not going // to resolve it here.... panic(err.Error()) } } } // EncryptMessageToGroup when given a message and a group, encrypts and signs the message under the group and // profile func (p *Profile) EncryptMessageToGroup(message string, groupID string) ([]byte, []byte, error) { if len(message) > MaxGroupMessageLength { return nil, nil, errors.New("group message is too long") } group := p.GetGroup(groupID) if group != nil { 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, err } dm := &groups.DecryptedGroupMessage{ Onion: p.Onion, Text: message, SignedGroupID: hexGroupID, Timestamp: uint64(timestamp), PreviousMessageSig: prevSig, Padding: padding[:], } ciphertext, err := group.EncryptMessage(dm) if err != nil { return nil, nil, err } serialized, _ := json.Marshal(dm) signature := p.SignMessage(groupID + group.GroupServer + base64.StdEncoding.EncodeToString(serialized)) group.AddSentMessage(dm, signature) return ciphertext, signature, nil } return nil, nil, errors.New("group does not exist") } // 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() defer p.lock.Unlock() newp := new(Profile) bytes, _ := json.Marshal(p) json.Unmarshal(bytes, &newp) if timeline { for groupID := range newp.Groups { newp.Groups[groupID].Timeline = *p.Groups[groupID].Timeline.GetCopy() } for peerID := range newp.Contacts { newp.Contacts[peerID].Timeline = *p.Contacts[peerID].Timeline.GetCopy() } } return newp }