package model import ( "crypto/rand" "crypto/rsa" "encoding/json" "git.mascherari.press/cwtch/protocol" "github.com/golang/protobuf/proto" "github.com/s-rah/go-ricochet/utils" "golang.org/x/crypto/ed25519" "io/ioutil" ) type PublicProfile struct { Name string Ed25519PublicKey ed25519.PublicKey } type Profile struct { PublicProfile Contacts map[string]PublicProfile Ed25519PrivateKey ed25519.PrivateKey OnionPrivateKey *rsa.PrivateKey Groups map[string]*Group } func GenerateNewProfile(name string) *Profile { p := new(Profile) p.Name = name pub, priv, _ := ed25519.GenerateKey(rand.Reader) p.Ed25519PublicKey = pub p.Ed25519PrivateKey = priv p.OnionPrivateKey, _ = utils.GeneratePrivateKey() p.Contacts = make(map[string]PublicProfile) p.Groups = make(map[string]*Group) return p } // GetCwtchIdentity returns the wire message for conveying this profiles identity. func (p *Profile) GetCwtchIdentityPacket() (message []byte) { ci := &protocol.CwtchIdentity{ Name: p.Name, Ed25519PublicKey: p.Ed25519PublicKey, } cpp := &protocol.CwtchPeerPacket{ CwtchIdentify: ci, } message, err := proto.Marshal(cpp) utils.CheckError(err) return } // GetCwtchIdentity returns the wire message for conveying this profiles identity. func (p *Profile) GetCwtchIdentity() (message []byte) { ci := &protocol.CwtchIdentity{ Name: p.Name, Ed25519PublicKey: p.Ed25519PublicKey, } message, err := proto.Marshal(ci) utils.CheckError(err) return } // AddCwtchIdentity takes a wire message and if it is a CwtchIdentity message adds the identity as a contact // otherwise returns an error func (p *Profile) AddCwtchIdentity(onion string, ci *protocol.CwtchIdentity) { p.AddContact(onion, PublicProfile{Name: ci.GetName(), Ed25519PublicKey: ci.GetEd25519PublicKey()}) } // AddContact allows direct manipulation of cwtch contacts func (p *Profile) AddContact(onion string, profile PublicProfile) { p.Contacts[onion] = profile } // VerifyMessage confirms the authenticity of a message given an onion, message and signature. func (p *Profile) VerifyMessage(onion string, message string, signature []byte) bool { return ed25519.Verify(p.Contacts[onion].Ed25519PublicKey, []byte(message), signature) } func (p *Profile) SignMessage(message string) []byte { sig := ed25519.Sign(p.Ed25519PrivateKey, []byte(message)) return sig } func (p *Profile) StartGroup(server string) (groupID string, invite []byte) { group := NewGroup(server) p.AddGroup(group) groupID = group.GroupID gci := &protocol.GroupChatInvite{ GroupName: groupID, GroupSharedKey: group.GroupKey[:], ServerHost: server, } cp := &protocol.CwtchPeerPacket{ GroupChatInvite: gci, } invite, err := proto.Marshal(cp) utils.CheckError(err) return } // ProcessInvite func (p *Profile) ProcessInvite(gci *protocol.GroupChatInvite) { group := new(Group) group.GroupID = gci.GetGroupName() copy(group.GroupKey[:], gci.GetGroupSharedKey()[:]) group.GroupServer = gci.GetServerHost() p.AddGroup(group) } // AddGroup func (p *Profile) AddGroup(group *Group) { p.Groups[group.GroupID] = group } // EncryptMessageToGroup func (p *Profile) AttemptDecryption(ciphertext []byte, signature []byte) (success bool, groupId string, onion string, message string) { for id, group := range p.Groups { success, message := group.DecryptMessage(ciphertext) if success { for onion := range p.Contacts { if p.VerifyMessage(onion, message+string(ciphertext), signature) { return true, id, onion, message } } return true, id, "not-verified", message } } return false, "", "", "" } // EncryptMessageToGroup func (p *Profile) EncryptMessageToGroup(message string, groupid string) (ciphertext []byte, signature []byte) { group := p.Groups[groupid] ciphertext = group.EncryptMessage(message) signature = p.SignMessage(message + string(ciphertext)) return } func (p *Profile) Save(profilefile string) error { bytes, _ := json.Marshal(p) return ioutil.WriteFile(profilefile, bytes, 0600) } func LoadProfile(profilefile string) (*Profile, error) { bytes, _ := ioutil.ReadFile(profilefile) profile := new(Profile) err := json.Unmarshal(bytes, &profile) return profile, err }