forked from cwtch.im/cwtch
150 lines
4.1 KiB
Go
150 lines
4.1 KiB
Go
|
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,
|
||
|
}
|
||
|
invite, err := proto.Marshal(gci)
|
||
|
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
|
||
|
}
|