2018-03-09 20:44:13 +00:00
package model
import (
"crypto/rand"
2018-10-05 03:18:34 +00:00
"encoding/base32"
2019-01-29 20:56:59 +00:00
"encoding/hex"
2019-01-21 20:11:40 +00:00
"encoding/json"
2020-02-10 22:09:24 +00:00
"git.openprivacy.ca/openprivacy/connectivity/tor"
2018-03-09 20:44:13 +00:00
"golang.org/x/crypto/ed25519"
2019-01-29 20:56:59 +00:00
"path/filepath"
2018-10-05 03:18:34 +00:00
"strings"
2018-05-30 17:41:02 +00:00
"sync"
2018-03-30 21:16:51 +00:00
"time"
2018-03-09 20:44:13 +00:00
)
2020-06-16 00:16:04 +00:00
// Authorization is a type determining client assigned authorization to a peer
type Authorization string
const (
2020-07-08 18:29:33 +00:00
// AuthUnknown is an initial state for a new unseen peer
2020-06-16 00:16:04 +00:00
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"
)
2018-03-15 16:33:26 +00:00
// PublicProfile is a local copy of a CwtchIdentity
2018-03-09 20:44:13 +00:00
type PublicProfile struct {
2019-10-18 23:56:10 +00:00
Name string
Ed25519PublicKey ed25519 . PublicKey
2020-06-16 00:16:04 +00:00
Authorization Authorization
DeprecatedBlocked bool ` json:"Blocked" `
2019-10-18 23:56:10 +00:00
Onion string
Attributes map [ string ] string
Timeline Timeline ` json:"-" `
LocalID string // used by storage engine
State string ` json:"-" `
lock sync . Mutex
2020-10-08 19:40:27 +00:00
UnacknowledgedMessages map [ string ] int
2018-03-09 20:44:13 +00:00
}
2018-03-15 16:33:26 +00:00
// Profile encapsulates all the attributes necessary to be a Cwtch Peer.
2018-03-09 20:44:13 +00:00
type Profile struct {
PublicProfile
2018-05-03 19:23:02 +00:00
Contacts map [ string ] * PublicProfile
2018-03-09 20:44:13 +00:00
Ed25519PrivateKey ed25519 . PrivateKey
Groups map [ string ] * Group
}
2018-11-28 19:50:32 +00:00
// MaxGroupMessageLength is the maximum length of a message posted to a server group.
// TODO: Should this be per server?
2019-01-19 21:09:38 +00:00
const MaxGroupMessageLength = 1800
2018-11-28 19:50:32 +00:00
2019-12-12 20:21:14 +00:00
// GenerateRandomID generates a random 16 byte hex id code
func GenerateRandomID ( ) string {
2019-01-29 20:56:59 +00:00
randBytes := make ( [ ] byte , 16 )
rand . Read ( randBytes )
return filepath . Join ( hex . EncodeToString ( randBytes ) )
}
2018-10-31 23:24:12 +00:00
func ( p * PublicProfile ) init ( ) {
2019-02-11 21:40:20 +00:00
if p . Attributes == nil {
p . Attributes = make ( map [ string ] string )
}
2020-10-08 19:40:27 +00:00
p . UnacknowledgedMessages = make ( map [ string ] int )
2019-12-12 20:21:14 +00:00
p . LocalID = GenerateRandomID ( )
2018-10-31 23:24:12 +00:00
}
// 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
}
2020-07-23 21:40:44 +00:00
// IsServer returns true if the profile is associated with a server.
func ( p * PublicProfile ) IsServer ( ) ( isServer bool ) {
_ , isServer = p . GetAttribute ( string ( KeyTypeServerOnion ) )
return
2020-07-22 17:14:32 +00:00
}
2018-10-31 23:24:12 +00:00
// 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
}
2018-03-15 16:33:26 +00:00
// GenerateNewProfile creates a new profile, with new encryption and signing keys, and a profile name.
2018-03-09 20:44:13 +00:00
func GenerateNewProfile ( name string ) * Profile {
p := new ( Profile )
2019-01-21 20:11:40 +00:00
p . init ( )
2018-03-09 20:44:13 +00:00
p . Name = name
pub , priv , _ := ed25519 . GenerateKey ( rand . Reader )
p . Ed25519PublicKey = pub
p . Ed25519PrivateKey = priv
2020-02-10 22:09:24 +00:00
p . Onion = tor . GetTorV3Hostname ( pub )
2018-03-09 20:44:13 +00:00
2018-05-03 19:23:02 +00:00
p . Contacts = make ( map [ string ] * PublicProfile )
2018-05-09 19:09:00 +00:00
p . Contacts [ p . Onion ] = & p . PublicProfile
2018-03-09 20:44:13 +00:00
p . Groups = make ( map [ string ] * Group )
return p
}
// AddContact allows direct manipulation of cwtch contacts
2018-05-03 19:23:02 +00:00
func ( p * Profile ) AddContact ( onion string , profile * PublicProfile ) {
2018-05-30 17:41:02 +00:00
p . lock . Lock ( )
2018-10-31 23:24:12 +00:00
profile . init ( )
2020-11-05 21:26:03 +00:00
// 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
}
}
2018-05-30 17:41:02 +00:00
p . lock . Unlock ( )
}
2019-10-18 23:56:10 +00:00
// 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 }
2020-10-07 20:53:22 +00:00
if contact . UnacknowledgedMessages == nil {
2020-10-08 19:40:27 +00:00
contact . UnacknowledgedMessages = make ( map [ string ] int )
2019-10-18 23:56:10 +00:00
}
2020-10-08 19:51:17 +00:00
contact . Timeline . Insert ( message )
2020-10-08 20:08:20 +00:00
contact . UnacknowledgedMessages [ eventID ] = contact . Timeline . Len ( ) - 1
2019-10-18 23:56:10 +00:00
return message
}
return nil
}
2018-11-21 21:23:59 +00:00
// AddMessageToContactTimeline allows the saving of a message sent via a direct connection chat to the profile.
2019-10-18 23:56:10 +00:00
func ( p * Profile ) AddMessageToContactTimeline ( onion string , messageTxt string , sent time . Time ) ( message * Message ) {
2018-11-21 21:23:59 +00:00
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 ( )
2019-10-18 23:56:10 +00:00
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
2021-05-26 17:07:08 +00:00
func ( p * Profile ) ErrorSentMessageToPeer ( onion string , eventID string , error string ) int {
2019-10-18 23:56:10 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
2018-11-21 21:23:59 +00:00
if ok {
2020-10-08 19:40:27 +00:00
mIdx , ok := contact . UnacknowledgedMessages [ eventID ]
2019-10-18 23:56:10 +00:00
if ok {
2021-05-26 17:07:08 +00:00
contact . Timeline . Messages [ mIdx ] . Error = error
2020-10-07 20:53:22 +00:00
delete ( contact . UnacknowledgedMessages , eventID )
2021-05-26 17:07:08 +00:00
return mIdx
2019-10-18 23:56:10 +00:00
}
}
2021-05-26 17:07:08 +00:00
return - 1
2019-10-18 23:56:10 +00:00
}
// AckSentMessageToPeer sets mesage to a peer as acknowledged
2021-05-03 18:35:35 +00:00
func ( p * Profile ) AckSentMessageToPeer ( onion string , eventID string ) int {
2019-10-18 23:56:10 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
contact , ok := p . Contacts [ onion ]
if ok {
2020-10-08 19:40:27 +00:00
mIdx , ok := contact . UnacknowledgedMessages [ eventID ]
2019-10-18 23:56:10 +00:00
if ok {
2020-10-08 20:08:20 +00:00
contact . Timeline . Messages [ mIdx ] . Acknowledged = true
2020-10-07 20:53:22 +00:00
delete ( contact . UnacknowledgedMessages , eventID )
2021-05-03 18:35:35 +00:00
return mIdx
2019-10-18 23:56:10 +00:00
}
}
2021-05-03 18:35:35 +00:00
return - 1
2019-10-18 23:56:10 +00:00
}
2021-08-25 19:16:50 +00:00
// VerifyGroupMessage confirms the authenticity of a message given an sender onion, message and signature.
2021-05-14 18:26:04 +00:00
// 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).
2021-11-11 00:41:43 +00:00
//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
//}
2018-03-30 21:16:51 +00:00
2018-03-15 16:33:26 +00:00
// SignMessage takes a given message and returns an Ed21159 signature
2018-03-09 20:44:13 +00:00
func ( p * Profile ) SignMessage ( message string ) [ ] byte {
sig := ed25519 . Sign ( p . Ed25519PrivateKey , [ ] byte ( message ) )
return sig
}
2021-11-11 00:41:43 +00:00
//// 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
//}
//
//
//// 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 {
// 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")
//}
//
2019-01-21 20:11:40 +00:00
2019-07-19 17:27:50 +00:00
// 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 {
2019-01-21 20:11:40 +00:00
p . lock . Lock ( )
defer p . lock . Unlock ( )
newp := new ( Profile )
bytes , _ := json . Marshal ( p )
json . Unmarshal ( bytes , & newp )
2019-07-19 17:27:50 +00:00
if timeline {
for groupID := range newp . Groups {
newp . Groups [ groupID ] . Timeline = * p . Groups [ groupID ] . Timeline . GetCopy ( )
}
2020-07-08 18:29:33 +00:00
for peerID := range newp . Contacts {
newp . Contacts [ peerID ] . Timeline = * p . Contacts [ peerID ] . Timeline . GetCopy ( )
}
2019-07-19 17:27:50 +00:00
}
2019-01-21 20:11:40 +00:00
return newp
}