2022-09-06 09:30:45 +02:00
|
|
|
package client
|
2022-01-15 22:19:29 +01:00
|
|
|
|
|
|
|
|
import (
|
2026-03-04 21:40:26 +01:00
|
|
|
"crypto/rand"
|
|
|
|
|
"encoding/base64"
|
2022-01-15 22:19:29 +01:00
|
|
|
"encoding/json"
|
2022-11-27 21:08:34 +01:00
|
|
|
"errors"
|
2026-03-04 21:40:26 +01:00
|
|
|
mrand "math/rand"
|
2022-12-02 23:18:13 +01:00
|
|
|
"os"
|
2024-01-12 23:17:34 +01:00
|
|
|
"path/filepath"
|
2026-02-02 15:32:36 +01:00
|
|
|
"strings"
|
|
|
|
|
"sync"
|
2023-01-11 21:42:14 +01:00
|
|
|
"time"
|
2022-01-15 22:19:29 +01:00
|
|
|
|
2022-09-06 09:30:45 +02:00
|
|
|
"forge.redroom.link/yves/meowlib"
|
2022-01-15 22:19:29 +01:00
|
|
|
"github.com/ProtonMail/gopenpgp/v2/helper"
|
2022-11-27 21:08:34 +01:00
|
|
|
"github.com/google/uuid"
|
2026-03-04 22:30:22 +01:00
|
|
|
doubleratchet "github.com/status-im/doubleratchet"
|
2022-01-15 22:19:29 +01:00
|
|
|
)
|
|
|
|
|
|
2023-01-11 21:42:14 +01:00
|
|
|
const maxHiddenCount = 30
|
|
|
|
|
|
2026-02-02 15:32:36 +01:00
|
|
|
// Package-level random number generator with mutex for thread-safe access
|
|
|
|
|
var (
|
|
|
|
|
rngMu sync.Mutex
|
2026-03-04 21:40:26 +01:00
|
|
|
rng = mrand.New(mrand.NewSource(time.Now().UnixNano()))
|
2026-02-02 15:32:36 +01:00
|
|
|
)
|
|
|
|
|
|
2021-10-18 21:05:44 +02:00
|
|
|
type Identity struct {
|
2026-02-02 15:11:41 +01:00
|
|
|
Nickname string `json:"nickname,omitempty"`
|
|
|
|
|
DefaultAvatar string `json:"default_avatar,omitempty"`
|
|
|
|
|
Avatars []Avatar `json:"avatars,omitempty"`
|
|
|
|
|
RootKp *meowlib.KeyPair `json:"id_kp,omitempty"`
|
|
|
|
|
Status string `json:"status,omitempty"`
|
|
|
|
|
Peers PeerStorage `json:"peers,omitempty"`
|
|
|
|
|
HiddenPeers [][]byte `json:"hidden_peers,omitempty"`
|
|
|
|
|
Personae PeerList `json:"faces,omitempty"`
|
|
|
|
|
Device *meowlib.KeyPair `json:"device,omitempty"`
|
|
|
|
|
KnownServers ServerList `json:"known_servers,omitempty"`
|
|
|
|
|
MessageServers ServerStorage `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"`
|
|
|
|
|
Uuid string `json:"uuid,omitempty"`
|
2023-01-08 23:19:08 +01:00
|
|
|
unlockedHiddenPeers PeerList
|
2021-10-18 21:05:44 +02:00
|
|
|
}
|
|
|
|
|
|
2024-03-29 18:26:41 +01:00
|
|
|
func CreateIdentity(nickname string) (*Identity, error) {
|
2021-10-18 21:05:44 +02:00
|
|
|
var id Identity
|
2026-02-02 15:11:41 +01:00
|
|
|
var err error
|
2022-01-15 22:19:29 +01:00
|
|
|
id.Nickname = nickname
|
2024-03-29 18:07:06 +01:00
|
|
|
id.Uuid = uuid.New().String()
|
2026-02-02 15:11:41 +01:00
|
|
|
id.RootKp, err = meowlib.NewKeyPair()
|
2023-01-07 00:39:05 +01:00
|
|
|
GetConfig().me = &id
|
2024-02-08 22:17:16 +01:00
|
|
|
id.MessageServers = ServerStorage{DbFile: uuid.NewString()}
|
2023-01-11 21:42:14 +01:00
|
|
|
id.generateRandomHiddenStuff()
|
2026-02-02 15:11:41 +01:00
|
|
|
err = id.CreateFolder()
|
2024-03-29 18:26:41 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &id, nil
|
2021-10-18 21:05:44 +02:00
|
|
|
}
|
|
|
|
|
|
2024-03-29 18:07:06 +01:00
|
|
|
func (id *Identity) CreateFolder() error {
|
|
|
|
|
err := os.MkdirAll(filepath.Join(GetConfig().StoragePath, id.Uuid), 0700)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-31 15:52:50 +02:00
|
|
|
func (id *Identity) WipeFolder() error {
|
2024-03-30 22:44:59 +01:00
|
|
|
err := os.RemoveAll(filepath.Join(GetConfig().StoragePath, id.Uuid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 18:50:04 +02:00
|
|
|
// InvitationStep1 creates a minimal pending peer with only a temporary keypair and returns
|
|
|
|
|
// the InvitationInitPayload to be transmitted to the invitee (via file, QR code, or server).
|
|
|
|
|
// Full keypairs are only generated in InvitationStep3, after the invitee's answer is received.
|
|
|
|
|
func (id *Identity) InvitationStep1(MyName string, ContactName string, MessageServerUids []string, InvitationMessage string) (*meowlib.InvitationInitPayload, *Peer, error) {
|
2021-10-18 21:05:44 +02:00
|
|
|
var peer Peer
|
2026-02-02 15:11:41 +01:00
|
|
|
var err error
|
2024-03-23 20:09:14 +01:00
|
|
|
peer.Uid = uuid.New().String()
|
2022-09-06 17:07:35 +02:00
|
|
|
peer.Name = ContactName
|
2024-02-10 15:05:02 +01:00
|
|
|
peer.MyName = MyName
|
2026-04-02 18:50:04 +02:00
|
|
|
peer.InvitationId = uuid.New().String()
|
2024-02-10 15:05:02 +01:00
|
|
|
peer.InvitationMessage = InvitationMessage
|
2026-04-02 18:50:04 +02:00
|
|
|
peer.MyPullServers = MessageServerUids
|
2026-03-04 22:30:22 +01:00
|
|
|
|
2026-04-02 18:50:04 +02:00
|
|
|
// Temporary keypair: public key is sent to invitee for step-2 encryption and as
|
|
|
|
|
// the server-side lookup key where the invitee will post their answer.
|
|
|
|
|
peer.InvitationKp, err = meowlib.NewKeyPair()
|
2026-03-04 22:30:22 +01:00
|
|
|
if err != nil {
|
2026-04-02 18:50:04 +02:00
|
|
|
return nil, nil, err
|
2026-03-04 22:30:22 +01:00
|
|
|
}
|
|
|
|
|
|
2024-05-28 16:47:04 +02:00
|
|
|
id.Peers.StorePeer(&peer)
|
2022-01-15 22:19:29 +01:00
|
|
|
|
2026-04-02 18:50:04 +02:00
|
|
|
payload := &meowlib.InvitationInitPayload{
|
|
|
|
|
Uuid: peer.InvitationId,
|
|
|
|
|
Name: MyName,
|
|
|
|
|
PublicKey: peer.InvitationKp.Public,
|
|
|
|
|
InvitationMessage: InvitationMessage,
|
2022-11-28 20:48:42 +01:00
|
|
|
}
|
2026-04-02 18:50:04 +02:00
|
|
|
return payload, &peer, nil
|
2022-11-28 20:48:42 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 18:50:04 +02:00
|
|
|
// InvitationStep2 creates the invitee's peer entry from the received InvitationInitPayload
|
|
|
|
|
// and returns the peer. The invitee generates their full keypairs here.
|
|
|
|
|
// The initiator's temporary public key (payload.PublicKey) is used both as the encryption
|
|
|
|
|
// target for the step-2 answer and as the server-side lookup address.
|
|
|
|
|
func (id *Identity) InvitationStep2(MyName string, ContactName string, MessageServerUids []string, payload *meowlib.InvitationInitPayload) (*Peer, error) {
|
2022-11-27 21:08:34 +01:00
|
|
|
var peer Peer
|
2026-02-02 15:11:41 +01:00
|
|
|
var err error
|
2024-03-23 20:09:14 +01:00
|
|
|
peer.Uid = uuid.New().String()
|
2026-02-02 15:11:41 +01:00
|
|
|
peer.MyIdentity, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.MyEncryptionKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.MyLookupKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-27 21:08:34 +01:00
|
|
|
if ContactName != "" {
|
|
|
|
|
peer.Name = ContactName
|
|
|
|
|
} else {
|
2026-04-02 18:50:04 +02:00
|
|
|
peer.Name = payload.Name
|
2024-02-10 22:36:25 +01:00
|
|
|
}
|
2026-04-02 18:50:04 +02:00
|
|
|
// The initiator's temp key is used for both encrypting the answer and as destination.
|
|
|
|
|
peer.ContactEncryption = payload.PublicKey
|
|
|
|
|
peer.ContactLookupKey = payload.PublicKey
|
|
|
|
|
peer.InvitationId = payload.Uuid
|
|
|
|
|
peer.InvitationMessage = payload.InvitationMessage
|
|
|
|
|
peer.MyPullServers = MessageServerUids
|
2024-02-10 15:05:02 +01:00
|
|
|
peer.MyName = MyName
|
2024-05-28 16:47:04 +02:00
|
|
|
id.Peers.StorePeer(&peer)
|
2026-02-02 15:11:41 +01:00
|
|
|
return &peer, nil
|
2022-11-27 21:08:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 18:50:04 +02:00
|
|
|
// InvitationStep3 is called by the initiator after receiving and decrypting the invitee's
|
|
|
|
|
// ContactCard (step-2 answer). It generates the initiator's full keypairs and DR material,
|
|
|
|
|
// updates the pending peer with the invitee's contact info, and returns the initiator's
|
|
|
|
|
// full ContactCard to be sent to the invitee (STEP_3_SEND).
|
|
|
|
|
func (id *Identity) InvitationStep3(inviteeContact *meowlib.ContactCard) (*meowlib.ContactCard, *Peer, error) {
|
2026-02-02 15:11:41 +01:00
|
|
|
var err error
|
2026-04-02 18:50:04 +02:00
|
|
|
peer := id.Peers.GetFromInvitationId(inviteeContact.InvitationId)
|
|
|
|
|
if peer == nil {
|
|
|
|
|
return nil, nil, errors.New("no pending peer found for invitation id " + inviteeContact.InvitationId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate full keypairs now that the invitee's identity is known.
|
|
|
|
|
peer.MyIdentity, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.MyEncryptionKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.MyLookupKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
symKeyBytes := make([]byte, 32)
|
|
|
|
|
if _, err = rand.Read(symKeyBytes); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.MySymKey = base64.StdEncoding.EncodeToString(symKeyBytes)
|
|
|
|
|
|
|
|
|
|
// Generate DR keypair and root key.
|
|
|
|
|
drKp, err := doubleratchet.DefaultCrypto{}.GenerateDH()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.DrKpPrivate = base64.StdEncoding.EncodeToString(drKp.PrivateKey())
|
|
|
|
|
peer.DrKpPublic = base64.StdEncoding.EncodeToString(drKp.PublicKey())
|
|
|
|
|
drRootKey := make([]byte, 32)
|
|
|
|
|
if _, err = rand.Read(drRootKey); err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
peer.DrRootKey = base64.StdEncoding.EncodeToString(drRootKey)
|
|
|
|
|
peer.DrInitiator = true
|
|
|
|
|
|
|
|
|
|
// Store invitee contact info.
|
|
|
|
|
peer.ContactPublicKey = inviteeContact.ContactPublicKey
|
|
|
|
|
peer.ContactEncryption = inviteeContact.EncryptionPublicKey
|
|
|
|
|
peer.ContactLookupKey = inviteeContact.LookupPublicKey
|
|
|
|
|
for _, srv := range inviteeContact.PullServers {
|
|
|
|
|
peer.ContactPullServers = append(peer.ContactPullServers, srv.GetUid())
|
|
|
|
|
newsrv, err := CreateServerFromUid(srv.GetUid())
|
|
|
|
|
if err == nil {
|
|
|
|
|
id.MessageServers.StoreServerIfNotExists(newsrv)
|
2022-11-27 21:08:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 18:50:04 +02:00
|
|
|
// Drop the temporary invitation keypair — no longer needed.
|
|
|
|
|
peer.InvitationKp = nil
|
|
|
|
|
|
|
|
|
|
id.Peers.StorePeer(peer)
|
|
|
|
|
|
|
|
|
|
return peer.GetMyContact(), peer, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InvitationStep4 is called by the invitee upon receiving the initiator's full ContactCard
|
|
|
|
|
// (carried as a regular UserMessage with invitation.step=3). It finalizes the peer entry.
|
|
|
|
|
func (id *Identity) InvitationStep4(initiatorContact *meowlib.ContactCard) error {
|
|
|
|
|
var err error
|
|
|
|
|
var newsrv *Server
|
|
|
|
|
for _, srv := range initiatorContact.PullServers {
|
|
|
|
|
newsrv, err = CreateServerFromUid(srv.GetUid())
|
2026-02-02 15:11:41 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
id.MessageServers.StoreServerIfNotExists(newsrv)
|
2024-06-05 14:45:01 +02:00
|
|
|
}
|
2026-04-02 18:50:04 +02:00
|
|
|
peer := id.Peers.GetFromInvitationId(initiatorContact.InvitationId)
|
|
|
|
|
if peer == nil {
|
|
|
|
|
return errors.New("no pending peer found for invitation id " + initiatorContact.InvitationId)
|
|
|
|
|
}
|
|
|
|
|
peer.ContactPublicKey = initiatorContact.ContactPublicKey
|
|
|
|
|
peer.ContactEncryption = initiatorContact.EncryptionPublicKey
|
|
|
|
|
peer.ContactLookupKey = initiatorContact.LookupPublicKey
|
|
|
|
|
peer.MySymKey = initiatorContact.SymetricKey
|
|
|
|
|
peer.DrRootKey = initiatorContact.DrRootKey
|
|
|
|
|
peer.ContactDrPublicKey = initiatorContact.DrPublicKey
|
|
|
|
|
peer.DrInitiator = false
|
|
|
|
|
for _, srv := range initiatorContact.PullServers {
|
|
|
|
|
peer.ContactPullServers = append(peer.ContactPullServers, srv.GetUid())
|
|
|
|
|
}
|
|
|
|
|
id.Peers.StorePeer(peer)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckInvitation checks if the received ContactCard is an answer to one of our pending
|
|
|
|
|
// invitations. Returns true when it is, with the proposed and received nicknames.
|
|
|
|
|
func (id *Identity) CheckInvitation(ReceivedContact *meowlib.ContactCard) (isAnswer bool, proposedNick string, receivedNick string, invitationMessage string) {
|
|
|
|
|
return id.Peers.CheckInvitation(ReceivedContact)
|
2021-10-18 21:05:44 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-08 22:17:16 +01:00
|
|
|
// LoadIdentity loads an identity from an encrypted file
|
2022-11-28 20:48:42 +01:00
|
|
|
func LoadIdentity(filename string, password string) (*Identity, error) {
|
2022-01-15 22:19:29 +01:00
|
|
|
var id Identity
|
2026-02-02 18:15:57 +01:00
|
|
|
err := GetConfig().SetMemPass(password)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2023-01-08 22:57:17 +01:00
|
|
|
GetConfig().IdentityFile = filename
|
2022-12-02 23:18:13 +01:00
|
|
|
indata, err := os.ReadFile(filename)
|
2022-01-15 22:19:29 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-28 20:48:42 +01:00
|
|
|
pass, err := helper.DecryptMessageWithPassword([]byte(password), string(indata))
|
2022-09-02 12:07:21 +02:00
|
|
|
if err != nil {
|
2022-01-15 22:19:29 +01:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-09-02 12:07:21 +02:00
|
|
|
err = json.Unmarshal([]byte(pass), &id)
|
2024-02-08 22:17:16 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-05-28 18:05:41 +02:00
|
|
|
GetConfig().me = &id
|
2024-05-28 17:32:26 +02:00
|
|
|
if id.Peers.DbFile != "" {
|
|
|
|
|
id.Peers.LoadPeers(password)
|
|
|
|
|
}
|
2022-01-15 22:19:29 +01:00
|
|
|
return &id, err
|
|
|
|
|
}
|
2021-10-18 21:05:44 +02:00
|
|
|
|
2022-11-28 20:48:42 +01:00
|
|
|
func (id *Identity) Save() error {
|
2023-02-15 22:08:17 +01:00
|
|
|
if GetConfig().IdentityFile == "" {
|
|
|
|
|
return errors.New("identity filename empty")
|
|
|
|
|
}
|
2022-01-15 22:19:29 +01:00
|
|
|
b, _ := json.Marshal(id)
|
2026-02-02 18:15:57 +01:00
|
|
|
password, err := GetConfig().GetMemPass()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
armor, err := helper.EncryptMessageWithPassword([]byte(password), string(b))
|
2022-01-15 22:19:29 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2023-01-08 22:57:17 +01:00
|
|
|
err = os.WriteFile(GetConfig().IdentityFile, []byte(armor), 0600)
|
2022-01-15 22:19:29 +01:00
|
|
|
return err
|
2021-10-18 21:05:44 +02:00
|
|
|
}
|
2023-01-08 23:19:08 +01:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
2023-01-11 22:29:31 +01:00
|
|
|
p.dbPassword = password
|
2024-03-02 10:07:59 +01:00
|
|
|
id.unlockedHiddenPeers = append(id.unlockedHiddenPeers, &p)
|
2023-01-08 23:19:08 +01:00
|
|
|
found = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if found {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return errors.New("no peer found")
|
|
|
|
|
}
|
2023-01-11 21:42:14 +01:00
|
|
|
|
2024-05-28 16:47:04 +02:00
|
|
|
/*
|
2023-01-11 21:42:14 +01:00
|
|
|
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
|
2024-05-28 16:47:04 +02:00
|
|
|
}*/
|
2023-01-11 21:42:14 +01:00
|
|
|
|
2026-02-02 15:11:41 +01:00
|
|
|
func (id *Identity) generateRandomHiddenStuff() error {
|
|
|
|
|
var err error
|
2026-02-02 15:32:36 +01:00
|
|
|
rngMu.Lock()
|
|
|
|
|
count := rng.Intn(maxHiddenCount) + 1
|
|
|
|
|
rngMu.Unlock()
|
2023-01-11 21:42:14 +01:00
|
|
|
for i := 1; i < count; i++ {
|
|
|
|
|
var p Peer
|
2024-05-28 16:47:04 +02:00
|
|
|
p.Uid = uuid.New().String()
|
2023-01-11 21:42:14 +01:00
|
|
|
p.Name = randomLenString(4, 20)
|
2026-02-02 15:11:41 +01:00
|
|
|
p.MyEncryptionKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
p.MyIdentity, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
p.MyLookupKp, err = meowlib.NewKeyPair()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-02-10 22:36:25 +01:00
|
|
|
p.Name = randomLenString(4, 20)
|
|
|
|
|
p.ContactPublicKey = p.MyLookupKp.Public
|
|
|
|
|
p.ContactEncryption = p.MyIdentity.Public
|
|
|
|
|
p.ContactLookupKey = p.MyEncryptionKp.Public
|
2024-05-28 16:47:04 +02:00
|
|
|
p.dbPassword = randomLenString(8, 14)
|
2024-02-10 22:36:25 +01:00
|
|
|
// p.Contact.AddUrls([]string{randomLenString(14, 60), randomLenString(14, 60)}) // todo add servers
|
2024-05-28 16:47:04 +02:00
|
|
|
id.Peers.StorePeer(&p)
|
|
|
|
|
//id.HidePeer(0, randomLenString(8, 14))
|
2024-01-12 23:17:34 +01:00
|
|
|
// TODO Add random conversations
|
2023-01-11 21:42:14 +01:00
|
|
|
}
|
2026-02-02 15:11:41 +01:00
|
|
|
return nil
|
2023-01-11 21:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-18 22:43:41 +01:00
|
|
|
type BackgroundJob struct {
|
2026-02-02 15:11:41 +01:00
|
|
|
RootPublic string `json:"root_public,omitempty"`
|
|
|
|
|
Device *meowlib.KeyPair `json:"device,omitempty"`
|
|
|
|
|
Jobs []RequestsJob `json:"jobs,omitempty"`
|
2024-01-18 22:43:41 +01:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 23:17:34 +01:00
|
|
|
type RequestsJob struct {
|
2026-02-02 15:11:41 +01:00
|
|
|
Server *Server `json:"server,omitempty"`
|
|
|
|
|
LookupKeys []*meowlib.KeyPair `json:"lookup_keys,omitempty"`
|
2024-01-12 23:17:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-05-07 20:44:41 +02:00
|
|
|
func (id *Identity) GetRequestJobs() []RequestsJob {
|
|
|
|
|
var list []RequestsJob
|
2024-01-12 23:17:34 +01:00
|
|
|
srvs := map[string]*RequestsJob{}
|
2024-02-12 20:02:02 +01:00
|
|
|
// get all servers
|
2024-02-08 22:17:16 +01:00
|
|
|
servers, err := id.MessageServers.LoadAllServers()
|
2024-02-12 20:02:02 +01:00
|
|
|
if err == nil {
|
|
|
|
|
// build a server map
|
2024-02-08 22:17:16 +01:00
|
|
|
for _, server := range servers {
|
|
|
|
|
var rj RequestsJob
|
|
|
|
|
rj.Server = server
|
|
|
|
|
srvs[server.GetServerCard().GetUid()] = &rj
|
2024-01-12 23:17:34 +01:00
|
|
|
}
|
2024-02-08 22:17:16 +01:00
|
|
|
// add ids to the map
|
2024-05-28 16:47:04 +02:00
|
|
|
peers, err := id.Peers.GetPeers()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, peer := range peers {
|
2024-02-08 22:17:16 +01:00
|
|
|
for _, server := range peer.MyPullServers {
|
2026-04-02 18:50:04 +02:00
|
|
|
if srvs[server] == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if peer.MyLookupKp != nil {
|
|
|
|
|
// Active peer — use the permanent lookup key.
|
|
|
|
|
srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp)
|
|
|
|
|
} else if peer.InvitationKp != nil {
|
|
|
|
|
// Step-1 pending peer — poll using the temp invitation keypair so the
|
|
|
|
|
// server-stored step-2 answer can be retrieved.
|
|
|
|
|
srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.InvitationKp)
|
|
|
|
|
}
|
2024-02-08 22:17:16 +01:00
|
|
|
}
|
2024-01-12 23:17:34 +01:00
|
|
|
}
|
2024-02-08 22:17:16 +01:00
|
|
|
// add hidden peers
|
|
|
|
|
for _, peer := range id.unlockedHiddenPeers {
|
|
|
|
|
for _, server := range peer.MyPullServers {
|
|
|
|
|
srvs[server].LookupKeys = append(srvs[server].LookupKeys, peer.MyLookupKp)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// todo add garbage
|
2024-01-12 23:17:34 +01:00
|
|
|
|
2024-02-08 22:17:16 +01:00
|
|
|
// todo random reorder
|
2024-01-12 23:17:34 +01:00
|
|
|
|
2024-02-08 22:17:16 +01:00
|
|
|
// build list
|
2024-02-12 20:02:02 +01:00
|
|
|
for _, srv := range srvs {
|
|
|
|
|
if len(srv.LookupKeys) > 0 {
|
2025-05-07 20:44:41 +02:00
|
|
|
list = append(list, *srv)
|
2024-02-12 20:02:02 +01:00
|
|
|
}
|
2024-02-08 22:17:16 +01:00
|
|
|
}
|
2024-01-12 23:17:34 +01:00
|
|
|
}
|
|
|
|
|
return list
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 22:43:41 +01:00
|
|
|
func (id *Identity) SaveBackgroundJob() error {
|
|
|
|
|
var bj BackgroundJob
|
|
|
|
|
bj.Jobs = id.GetRequestJobs()
|
|
|
|
|
bj.RootPublic = id.RootKp.Public
|
|
|
|
|
bj.Device = id.Device
|
|
|
|
|
jsonjobs, err := json.Marshal(bj)
|
2024-01-12 23:17:34 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-03-29 18:07:06 +01:00
|
|
|
id.CreateFolder()
|
|
|
|
|
err = os.WriteFile(filepath.Join(GetConfig().StoragePath, id.Uuid, ".jobs"), jsonjobs, 0600)
|
2024-01-12 23:17:34 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-11 21:42:14 +01:00
|
|
|
func randomLenString(min int, max int) string {
|
2026-02-02 15:32:36 +01:00
|
|
|
rngMu.Lock()
|
|
|
|
|
defer rngMu.Unlock()
|
|
|
|
|
|
|
|
|
|
length := rng.Intn(max-min+1) + min
|
|
|
|
|
return randomStringLocked(length)
|
2023-01-11 21:42:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func randomString(n int) string {
|
2026-02-02 15:32:36 +01:00
|
|
|
rngMu.Lock()
|
|
|
|
|
defer rngMu.Unlock()
|
|
|
|
|
|
|
|
|
|
return randomStringLocked(n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// randomStringLocked generates a random string of length n.
|
|
|
|
|
// Must be called with rngMu already locked.
|
|
|
|
|
func randomStringLocked(n int) string {
|
|
|
|
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
|
|
|
|
|
|
var b strings.Builder
|
|
|
|
|
b.Grow(n)
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
b.WriteByte(letterBytes[rng.Intn(len(letterBytes))])
|
2023-01-11 21:42:14 +01:00
|
|
|
}
|
2026-02-02 15:32:36 +01:00
|
|
|
return b.String()
|
2023-01-11 21:42:14 +01:00
|
|
|
}
|