2018-03-09 20:44:13 +00:00
|
|
|
package model
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
2021-11-16 23:06:30 +00:00
|
|
|
"cwtch.im/cwtch/protocol/groups"
|
|
|
|
"encoding/base64"
|
2019-01-29 20:56:59 +00:00
|
|
|
"encoding/hex"
|
2019-01-21 20:11:40 +00:00
|
|
|
"encoding/json"
|
2021-11-16 23:06:30 +00:00
|
|
|
"errors"
|
|
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
2018-03-09 20:44:13 +00:00
|
|
|
"golang.org/x/crypto/ed25519"
|
2021-11-16 23:06:30 +00:00
|
|
|
"io"
|
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
|
|
|
|
2021-11-16 23:06:30 +00:00
|
|
|
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 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
|
|
|
|
}
|
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
|
|
|
|
}
|