package client import ( "encoding/json" "errors" "math/rand" "os" "time" "forge.redroom.link/yves/meowlib" "github.com/ProtonMail/gopenpgp/v2/helper" "github.com/google/uuid" ) const maxHiddenCount = 30 type Identity struct { Nickname string `json:"nickname,omitempty"` DefaultAvatar string `json:"default_avatar,omitempty"` RootKp meowlib.KeyPair `json:"id_kp,omitempty"` Status string `json:"status,omitempty"` Peers PeerList `json:"peers,omitempty"` HiddenPeers [][]byte `json:"hiddend_peers,omitempty"` Device meowlib.KeyPair `json:"device,omitempty"` KnownServers ServerList `json:"known_servers,omitempty"` MessageServers ServerList `json:"message_servers,omitempty"` DefaultDbPassword string `json:"default_db_password,omitempty"` DbPasswordStore bool `json:"db_password_store,omitempty"` OwnedDevices PeerList `json:"owned_devices,omitempty"` StaticMtkServerPaths []ServerList `json:"static_mtk_server_paths,omitempty"` DynamicMtkServeRules []string `json:"dynamic_mtk_serve_rules,omitempty"` InvitationTimeout int `json:"invitation_timeout,omitempty"` unlockedHiddenPeers PeerList } func CreateIdentity(nickname string) *Identity { var id Identity id.Nickname = nickname id.RootKp = meowlib.NewKeyPair() GetConfig().me = &id id.generateRandomHiddenStuff() return &id } // Creates an invitation for a peer, returns the peer containing func (id *Identity) InvitePeer(MyName string, ContactName string, MessageServerIdxs []int) (*Peer, *meowlib.ContactCard, error) { var peer Peer var myContactCard meowlib.ContactCard peer.MyIdentity = meowlib.NewKeyPair() peer.MyEncryptionKp = meowlib.NewKeyPair() peer.MyLookupKp = meowlib.NewKeyPair() peer.Name = ContactName peer.InvitationId = uuid.New().String() if id.MessageServers.Servers == nil { return nil, nil, errors.New("no message servers defined in your identity") } for _, i := range MessageServerIdxs { if i > len(id.MessageServers.Servers)-1 { return nil, nil, errors.New("requested server out of range of defined message servers") } } for _, i := range MessageServerIdxs { srv := &id.MessageServers.Servers[i].ServerData myContactCard.PullServers = append(myContactCard.PullServers, srv) } myContactCard.Name = MyName myContactCard.ContactPublicKey = peer.MyIdentity.Public myContactCard.EncryptionPublicKey = peer.MyEncryptionKp.Public myContactCard.LookupPublicKey = peer.MyLookupKp.Public myContactCard.InvitationId = peer.InvitationId id.Peers = append(id.Peers, peer) return &peer, &myContactCard, nil } func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string) { for _, p := range id.Peers { if p.InvitationId == ReceivedContact.InvitationId { return true, p.Name, ReceivedContact.Name } } return false, "", ReceivedContact.Name } func (id *Identity) AnswerInvitation(MyName string, ContactName string, MessageServerIdxs []int, ReceivedContact *meowlib.ContactCard) *meowlib.ContactCard { var peer Peer var myContactCard meowlib.ContactCard peer.MyIdentity = meowlib.NewKeyPair() peer.MyEncryptionKp = meowlib.NewKeyPair() peer.MyLookupKp = meowlib.NewKeyPair() if ContactName != "" { peer.Name = ContactName } else { peer.Name = ReceivedContact.Name } peer.Contact = *ReceivedContact for _, i := range MessageServerIdxs { srv := id.MessageServers.Servers[i].ServerData myContactCard.PullServers = append(myContactCard.PullServers, &srv) } myContactCard.Name = MyName myContactCard.ContactPublicKey = peer.MyIdentity.Public myContactCard.EncryptionPublicKey = peer.MyEncryptionKp.Public myContactCard.LookupPublicKey = peer.MyLookupKp.Public myContactCard.InvitationId = ReceivedContact.InvitationId id.Peers = append(id.Peers, peer) return &myContactCard } func (id *Identity) FinalizeInvitation(ReceivedContact *meowlib.ContactCard) error { for i, p := range id.Peers { if p.InvitationId == ReceivedContact.InvitationId { id.Peers[i].Contact = *ReceivedContact return nil } } return errors.New("no matching contact found for invitationId " + ReceivedContact.InvitationId) } func LoadIdentity(filename string, password string) (*Identity, error) { var id Identity GetConfig().memoryPassword = password GetConfig().IdentityFile = filename indata, err := os.ReadFile(filename) if err != nil { return nil, err } pass, err := helper.DecryptMessageWithPassword([]byte(password), string(indata)) if err != nil { return nil, err } err = json.Unmarshal([]byte(pass), &id) GetConfig().me = &id return &id, err } func (id *Identity) Save() error { if GetConfig().IdentityFile == "" { return errors.New("identity filename empty") } b, _ := json.Marshal(id) armor, err := helper.EncryptMessageWithPassword([]byte(GetConfig().memoryPassword), string(b)) if err != nil { return err } err = os.WriteFile(GetConfig().IdentityFile, []byte(armor), 0600) return err } func (id *Identity) TryUnlockHidden(password string) error { found := false for _, encPeer := range id.HiddenPeers { p := Peer{} jsonPeer, err := meowlib.SymDecrypt(password, encPeer) if err == nil { err = json.Unmarshal(jsonPeer, &p) if err != nil { return err } p.dbPassword = password id.unlockedHiddenPeers = append(id.unlockedHiddenPeers, p) found = true } } if found { return nil } return errors.New("no peer found") } func (id *Identity) HidePeer(peerIdx int, password string) error { serializedPeer, err := json.Marshal(id.Peers[peerIdx]) if err != nil { return err } encrypted, err := meowlib.SymEncrypt(password, serializedPeer) if err != nil { return err } // add encrypted peer data id.HiddenPeers = append(id.HiddenPeers, encrypted) // remove clear text peer id.Peers = append(id.Peers[:peerIdx], id.Peers[peerIdx+1:]...) return nil } func (id *Identity) generateRandomHiddenStuff() { rand.Seed(time.Now().UnixNano()) count := rand.Intn(maxHiddenCount) + 1 for i := 1; i < count; i++ { var p Peer p.Name = randomLenString(4, 20) p.MyEncryptionKp = meowlib.NewKeyPair() p.MyIdentity = meowlib.NewKeyPair() p.MyLookupKp = meowlib.NewKeyPair() p.Contact.Name = randomLenString(4, 20) p.Contact.ContactPublicKey = p.MyLookupKp.Public p.Contact.EncryptionPublicKey = p.MyIdentity.Public p.Contact.LookupPublicKey = p.MyEncryptionKp.Public p.Contact.AddUrls([]string{randomLenString(14, 60), randomLenString(14, 60)}) id.Peers = append(id.Peers, p) id.HidePeer(0, randomLenString(8, 14)) // TODO Add conversations } } func randomLenString(min int, max int) string { rand.Seed(time.Now().UnixNano()) n := rand.Intn(max-min) + min return randomString(n) } func randomString(n int) string { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") s := make([]rune, n) for i := range s { s[i] = letters[rand.Intn(len(letters))] } return string(s) }