func LoadRatchet(d *Daemon, name string, fillAuth func(tag, data []byte, theirAuthPublic *[32]byte), checkAuth func(tag, data, msg []byte, ourAuthPrivate *[32]byte) error) (*ratchet.Ratchet, error) { ratch := new(ratchet.Ratchet) if err := persistence.UnmarshalFromFile(d.ratchetPath(name), ratch); err != nil { return nil, err } ratch.FillAuth = fillAuth ratch.CheckAuth = checkAuth return ratch, nil }
func (d *Daemon) LatestProfile(name string, received *dename.Profile) (*dename.Profile, error) { stored := new(dename.Profile) err := persistence.UnmarshalFromFile(d.profilePath(name), stored) if err != nil { stored = nil } if received != nil && (stored == nil || *received.Version > *stored.Version) { return received, d.MarshalToFile(d.profilePath(name), received) } return stored, nil }
func PrepareTestAccountDaemon(name string, rootDir string, denameConfig *denameClient.Config, serverAddr string, serverPk *[32]byte, t testing.TB) *Daemon { dnmClient, err := denameClient.NewClient(denameConfig, nil, nil) //get port from serverAddr addr, portStr, err := net.SplitHostPort(serverAddr) if err != nil { t.Fatal(err) } var port int if _, err := fmt.Sscanf(portStr, "%d", &port); err != nil { t.Fatal(err) } //create the accounts with Init torAddr := "DANGEROUS_NO_TOR" err = Init(rootDir, name, addr, port, serverPk, torAddr) if err != nil { t.Fatal(err) } //initialize daemon with Load theDaemon, err := Load(rootDir, denameConfig) if err != nil { t.Fatal(err) } cbProfile := new(proto.Profile) if err := persistence.UnmarshalFromFile(theDaemon.OurChatterboxProfilePath(), cbProfile); err != nil { t.Fatal(err) } chatProfileBytes, err := protobuf.Marshal(cbProfile) if err != nil { t.Fatal(err) } denameProfile, sk, err := denameClient.NewProfile(nil, nil) if err != nil { t.Fatal(err) } if err := denameClient.SetProfileField(denameProfile, cbClient.PROFILE_FIELD_ID, chatProfileBytes); err != nil { t.Fatal(err) } err = dnmClient.Register(sk, name, denameProfile, testutil2.MakeToken()) if err != nil { t.Fatal(err) } return theDaemon }
func (d *Daemon) sendMessage(msg []byte, theirDename string, msgRatch *ratchet.Ratchet) error { profile := new(dename.Profile) err := persistence.UnmarshalFromFile(d.profilePath(theirDename), profile) if err != nil { return err } chatProfileBytes, err := client.GetProfileField(profile, util.PROFILE_FIELD_ID) if err != nil { return err } chatProfile := new(proto.Profile) if err := chatProfile.Unmarshal(chatProfileBytes); err != nil { return err } addr := chatProfile.ServerAddressTCP port := (int)(chatProfile.ServerPortTCP) pkTransport := (*[32]byte)(&chatProfile.ServerTransportPK) theirPk := (*[32]byte)(&chatProfile.UserIDAtServer) if err != nil { return err } theirInBuf := make([]byte, proto.SERVER_MESSAGE_SIZE) encMsg, ratch, err := util.EncryptAuth(msg, msgRatch) if err != nil { return err } theirConn, err := d.cc.DialServer(theirDename, addr, port, pkTransport, nil, nil) if err != nil { return err } if err := StoreRatchet(d, theirDename, ratch); err != nil { theirConn.Close() d.cc.PutClose(theirDename) return err } err = util.UploadMessageToUser(theirConn, theirInBuf, theirPk, encMsg) if err != nil { theirConn.Close() d.cc.PutClose(theirDename) return err } d.cc.Put(theirDename, theirConn) return nil }
func AllRatchets(d *Daemon, fillAuth func(tag, data []byte, theirAuthPublic *[32]byte), checkAuth func(tag, data, msg []byte, ourAuthPrivate *[32]byte) error) ([]*ratchet.Ratchet, error) { files, err := ioutil.ReadDir(d.ratchetKeysDir()) if err != nil { return nil, err } ret := make([]*ratchet.Ratchet, 0, len(files)) for _, file := range files { if file.IsDir() { continue } ratch := new(ratchet.Ratchet) err := persistence.UnmarshalFromFile(filepath.Join(d.ratchetKeysDir(), file.Name()), ratch) if err != nil { return nil, fmt.Errorf("failed to parse ratchet for \"%s\": %s", file.Name(), err) } ratch.FillAuth = fillAuth ratch.CheckAuth = checkAuth ret = append(ret, ratch) } return ret, nil }
func LoadPrekeys(d *Daemon) ([]*[32]byte, []*[32]byte, error) { prekeysProto := new(proto.Prekeys) err := persistence.UnmarshalFromFile(d.prekeysPath(), prekeysProto) if err != nil { if os.IsNotExist(err) { return nil, nil, nil } return nil, nil, err } if len(prekeysProto.PrekeyPublics) != len(prekeysProto.PrekeySecrets) { return nil, nil, fmt.Errorf("len(prekeysProto.prekeyPublics) != len(prekeysProto.prekeySecrets)") } // convert protobuf proto.Byte32 to *[32]byte prekeySecrets := make([]*[32]byte, len(prekeysProto.PrekeySecrets)) prekeyPublics := make([]*[32]byte, len(prekeysProto.PrekeyPublics)) for i := 0; i < len(prekeySecrets); i++ { prekeySecrets[i] = (*[32]byte)(&prekeysProto.PrekeySecrets[i]) prekeyPublics[i] = (*[32]byte)(&prekeysProto.PrekeyPublics[i]) } return prekeyPublics, prekeySecrets, nil }
func (d *Daemon) processOutboxDir(dirname string) error { // TODO: refactor: separate message assembly and filesystem access? // parse metadata metadataFile := filepath.Join(dirname, persistence.MetadataFileName) if _, err := os.Stat(metadataFile); err != nil { return nil // no metadata --> not an outgoing message } metadata := proto.ConversationMetadata{} err := persistence.UnmarshalFromFile(metadataFile, &metadata) if err != nil { return err } metadata.Participants = append(metadata.Participants, d.Dename) undupStrings(metadata.Participants) sort.Strings(metadata.Participants) convName := persistence.ConversationName(&metadata) // load messages potentialMessages, err := ioutil.ReadDir(dirname) if err != nil { return err } messages := make([][]byte, 0, len(potentialMessages)) for _, finfo := range potentialMessages { if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName { msg, err := ioutil.ReadFile(filepath.Join(dirname, finfo.Name())) if err != nil { return err } // make protobuf for message; append it d.ourDenameLookupMu.Lock() payload := proto.Message{ Dename: d.Dename, DenameLookup: d.ourDenameLookup, Contents: msg, Subject: metadata.Subject, Participants: metadata.Participants, Date: finfo.ModTime().UnixNano(), } d.ourDenameLookupMu.Unlock() payloadBytes, err := payload.Marshal() if err != nil { return err } messages = append(messages, payloadBytes) } } if len(messages) == 0 { return nil // no messages to send, just the metadata file } if err := d.conversationToConversations(&metadata); err != nil && !os.IsExist(err) && !strings.Contains(fmt.Sprint(err), "directory not empty") { log.Fatal(err) } for _, recipient := range metadata.Participants { if recipient == d.Dename { continue } for _, msg := range messages { if err != nil { return err } if msgRatch, err := LoadRatchet(d, recipient, d.fillAuth, d.checkAuth); err != nil { //First message to this recipien if err := d.sendFirstMessage(msg, recipient); err != nil { return err } } else { if err := d.sendMessage(msg, recipient, msgRatch); err != nil { return err } } } } // move the sent messages to the conversation folder for _, finfo := range potentialMessages { if !finfo.IsDir() && finfo.Name() != persistence.MetadataFileName { if err = os.Rename(filepath.Join(dirname, finfo.Name()), filepath.Join(d.ConversationDir(), persistence.ConversationName(&metadata), persistence.MessageName(finfo.ModTime(), string(d.Dename)))); err != nil { log.Fatal(err) } } } // canonicalize the outbox folder name if dirname != filepath.Join(d.OutboxDir(), convName) { if err := os.Rename(dirname, filepath.Join(d.OutboxDir(), convName)); err != nil { shred.RemoveAll(dirname) } } return nil }
// run executes the main loop of the chatterbox daemon func (d *Daemon) run() error { profile := new(proto.Profile) if err := persistence.UnmarshalFromFile(d.OurChatterboxProfilePath(), profile); err != nil { return err } ourConn, err := d.cc.DialServer(d.Dename, d.ServerAddressTCP, int(d.ServerPortTCP), (*[32]byte)(&d.ServerTransportPK), (*[32]byte)(&profile.UserIDAtServer), (*[32]byte)(&d.TransportSecretKeyForServer)) if err != nil { return err } defer ourConn.Close() notifies := make(chan *util.EnvelopeWithId) replies := make(chan *proto.ServerToClient) connToServer := &util.ConnectionToServer{ InBuf: d.inBuf, Conn: ourConn, ReadReply: replies, ReadEnvelope: notifies, } go connToServer.ReceiveMessages() prekeyPublics, prekeySecrets, err := d.updatePrekeys(connToServer) if err != nil { return err } watcher, err := fsnotify.NewWatcher() if err != nil { return err } defer watcher.Close() initFn := func(path string, f os.FileInfo, err error) error { if f.IsDir() { return d.processOutboxDir(path) } return d.processOutboxDir(filepath.Dir(path)) } err = WatchDir(watcher, d.OutboxDir(), initFn) if err != nil { return err } if err = util.EnablePush(connToServer); err != nil { return err } d.requestAllMessages(connToServer) for { select { case <-d.stop: return nil case ev := <-watcher.Event: // event in the directory structure; watch any new directories if _, err = os.Stat(ev.Name); err == nil { err = WatchDir(watcher, ev.Name, initFn) if err != nil { log.Printf("watch %s: %s", ev.Name, err) // TODO } d.processOutboxDir(ev.Name) } case envelopewithid := <-connToServer.ReadEnvelope: envelope := envelopewithid.Envelope id := envelopewithid.Id msgHash := sha256.Sum256(envelope) // assume it's the first message we're receiving from the person; try to decrypt message, ratch, index, err := d.decryptFirstMessage(envelope, prekeyPublics, prekeySecrets) if err == nil { // assumption was correct, found a prekey that matched if err := StoreRatchet(d, message.Dename, ratch); err != nil { return err } newPrekeyPublics := append(prekeyPublics[:index], prekeyPublics[index+1:]...) newPrekeySecrets := append(prekeySecrets[:index], prekeySecrets[index+1:]...) if err = StorePrekeys(d, newPrekeyPublics, newPrekeySecrets); err != nil { return err } //TODO: Update prekeys by removing index, store // is this done? What does it mean? if err := d.saveMessage(message); err != nil { return err } if err := util.DeleteMessages(connToServer, []*[32]byte{id}); err != nil { return err } } else { // try decrypting with a ratchet ratchets, err := AllRatchets(d, d.fillAuth, d.checkAuth) if err != nil { return err } // TODO: figure out what here should be atomic and comment if message, ratch, err := decryptMessage(envelope, ratchets); err == nil { if err := d.saveMessage(message); err != nil { return err } if err := StoreRatchet(d, message.Dename, ratch); err != nil { return err } } else { log.Printf("failed to decrypt %x: %s", msgHash, err) } if err := util.DeleteMessages(connToServer, []*[32]byte{id}); err != nil { return err } } case err := <-watcher.Error: if err != nil { return err } } } }
// Load initializes a chatterbox daemon from rootDir func Load(rootDir string, denameConfig *client.Config) (*Daemon, error) { d := &Daemon{ Paths: persistence.Paths{ RootDir: rootDir, Application: "daemon", }, Now: time.Now, inBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), outBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), } if err := persistence.UnmarshalFromFile(d.configPath(), &d.LocalAccountConfig); err != nil { return nil, err } d.cc = util.NewConnectionCache(util.NewAnonDialer(d.TorAddress)) if err := persistence.UnmarshalFromFile(d.AccountPath(), &d.LocalAccount); err != nil { return nil, err } if err := persistence.UnmarshalFromFile(d.configPath(), &d.LocalAccountConfig); err != nil { return nil, err } d.ourDenameLookup = new(dename.ClientReply) persistence.UnmarshalFromFile(d.ourDenameLookupReplyPath(), d.ourDenameLookup) // ensure that we have a correct directory structure // including a correctly-populated outbox if err := InitFs(d); err != nil { return nil, err } ourDenameClient, err := client.NewClient(denameConfig, util.NewAnonDialer(d.TorAddress), nil) if err != nil { return nil, err } d.foreignDenameClient, err = client.NewClient(denameConfig, util.NewAnonDialer(d.TorAddress), nil) if err != nil { return nil, err } if denameConfig == nil { denameConfig = &client.DefaultConfig } timelessCfg := *denameConfig // TODO: make very sure this is a deep copy timelessCfg.Freshness.Threshold = fmt.Sprintf("%dh", 100*365*24) d.timelessDenameClient, err = client.NewClient(&timelessCfg, nil, nil) if err != nil { return nil, err } d.psd, err = profilesyncd.New(ourDenameClient, 10*time.Minute, d.Dename, d.onOurDenameProfileDownload, nil) if err != nil { return nil, err } d.fillAuth = util.FillAuthWith((*[32]byte)(&d.MessageAuthSecretKey)) d.checkAuth = util.CheckAuthWith(d.ProfileRatchet) return d, nil }