func (d *Daemon) decryptFirstMessage(envelope []byte, pkList []*[32]byte, skList []*[32]byte) (*proto.Message, *ratchet.Ratchet, int, error) { skAuth := (*[32]byte)(&d.MessageAuthSecretKey) ratch, msg, index, err := util.DecryptAuthFirst(envelope, pkList, skList, skAuth, d.ProfileRatchet) if err != nil { return nil, nil, -1, err } message := new(proto.Message) if err := message.Unmarshal(msg); err != nil { return nil, nil, -1, err } return message, ratch, index, nil }
func decryptMessage(envelope []byte, ratchets []*ratchet.Ratchet) (*proto.Message, *ratchet.Ratchet, error) { var ratch *ratchet.Ratchet var msg []byte var err error for _, msgRatch := range ratchets { ratch, msg, err = util.DecryptAuth(envelope, msgRatch) if err == nil { break // found the right ratchet } } if msg == nil { return nil, nil, fmt.Errorf("could not find suitable ratchet: %v", err) } message := new(proto.Message) if err := message.Unmarshal(msg); err != nil { return nil, nil, err } return message, ratch, nil }
func CheckAuthWith(prt ProfileRatchet) func([]byte, []byte, []byte, *[32]byte) error { return func(tag, data, msg []byte, ourAuthPrivate *[32]byte) error { var sharedAuthKey [32]byte message := new(proto.Message) unpadMsg := proto.Unpad(msg) err := message.Unmarshal(unpadMsg) if err != nil { return err } profile, err := prt(message.Dename, message.DenameLookup) if err != nil { return err } chatProfileBytes, err := client.GetProfileField(profile, PROFILE_FIELD_ID) if err != nil { return err } chatProfile := new(proto.Profile) if err := chatProfile.Unmarshal(chatProfileBytes); err != nil { return err } theirAuthPublic := (*[32]byte)(&chatProfile.MessageAuthKey) curve25519.ScalarMult(&sharedAuthKey, ourAuthPrivate, theirAuthPublic) h := hmac.New(sha256.New, sharedAuthKey[:]) h.Write(data) if subtle.ConstantTimeCompare(tag, h.Sum(nil)[:len(tag)]) == 0 { return errors.New("Authentication failed: failed to reproduce envelope auth tag using the current auth pubkey from dename") } return nil } }
func TestEncryptFirstMessage(t *testing.T) { alice := "alice" bob := "bob" denameConfig, denameTeardown := denameTestutil.SingleServer(t) defer denameTeardown() aliceDnmc, err := denameClient.NewClient(denameConfig, nil, nil) bobDnmc, err := denameClient.NewClient(denameConfig, nil, nil) if err != nil { t.Fatal(err) } _, serverPubkey, serverAddr, serverTeardown := server.CreateTestServer(t) defer serverTeardown() aliceDir, err := ioutil.TempDir("", "daemon-alice") if err != nil { t.Fatal(err) } defer shred.RemoveAll(aliceDir) bobDir, err := ioutil.TempDir("", "daemon-bob") if err != nil { t.Fatal(err) } defer shred.RemoveAll(bobDir) aliceConf := &Daemon{ Paths: persistence.Paths{ RootDir: aliceDir, Application: "daemon", }, Now: time.Now, foreignDenameClient: aliceDnmc, timelessDenameClient: aliceDnmc, inBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), outBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), LocalAccountConfig: proto.LocalAccountConfig{}, LocalAccount: proto.LocalAccount{ Dename: alice, }, cc: util.NewConnectionCache(util.NewAnonDialer("DANGEROUS_NO_TOR")), } bobConf := &Daemon{ Paths: persistence.Paths{ RootDir: bobDir, Application: "daemon", }, Now: time.Now, foreignDenameClient: bobDnmc, timelessDenameClient: bobDnmc, inBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), outBuf: make([]byte, proto.SERVER_MESSAGE_SIZE), LocalAccountConfig: proto.LocalAccountConfig{}, LocalAccount: proto.LocalAccount{ Dename: bob, }, cc: util.NewConnectionCache(util.NewAnonDialer("DANGEROUS_NO_TOR")), } aliceHomeConn := util.CreateTestAccount(alice, aliceDnmc, &aliceConf.LocalAccountConfig, serverAddr, serverPubkey, t) defer aliceHomeConn.Close() bobHomeConn := util.CreateTestAccount(bob, bobDnmc, &bobConf.LocalAccountConfig, serverAddr, serverPubkey, t) defer bobHomeConn.Close() //fmt.Printf("CBob: %v\n", ([32]byte)(bobConf.TransportSecretKeyForServer)) aliceNotifies := make(chan *util.EnvelopeWithId) aliceReplies := make(chan *proto.ServerToClient) aliceConnToServer := &util.ConnectionToServer{ InBuf: aliceConf.inBuf, Conn: aliceHomeConn, ReadReply: aliceReplies, ReadEnvelope: aliceNotifies, } go aliceConnToServer.ReceiveMessages() bobNotifies := make(chan *util.EnvelopeWithId) bobReplies := make(chan *proto.ServerToClient) bobConnToServer := &util.ConnectionToServer{ InBuf: bobConf.inBuf, Conn: bobHomeConn, ReadReply: bobReplies, ReadEnvelope: bobNotifies, } go bobConnToServer.ReceiveMessages() if err := InitFs(aliceConf); err != nil { t.Fatal(err) } if err := InitFs(bobConf); err != nil { t.Fatal(err) } //Bob uploads keys bobPublicPrekeys, bobSecretPrekeys, err := GeneratePrekeys(maxPrekeys) var bobSigningKey [64]byte copy(bobSigningKey[:], bobConf.KeySigningSecretKey[:64]) err = util.UploadKeys(bobConnToServer, util.SignKeys(bobPublicPrekeys, &bobSigningKey)) if err != nil { t.Fatal(err) } //Bob enables notifications if err = util.EnablePush(bobConnToServer); err != nil { t.Fatal(err) } //Alice uploads keys alicePublicPrekeys, _, err := GeneratePrekeys(maxPrekeys) var aliceSigningKey [64]byte copy(aliceSigningKey[:], aliceConf.KeySigningSecretKey[:64]) err = util.UploadKeys(aliceConnToServer, util.SignKeys(alicePublicPrekeys, &aliceSigningKey)) if err != nil { t.Fatal(err) } //Alice enables notification if err = util.EnablePush(aliceConnToServer); err != nil { t.Fatal(err) } participants := make([]string, 0, 2) participants = append(participants, alice) participants = append(participants, bob) msg1 := []byte("Envelope") payload := proto.Message{ Subject: "Subject1", Participants: participants, Dename: alice, Contents: msg1, } envelope, err := payload.Marshal() if err != nil { t.Fatal(err) } err = aliceConf.sendFirstMessage(envelope, bob) if err != nil { t.Fatal(err) } incoming := <-bobConnToServer.ReadEnvelope out, bobRatch, _, err := bobConf.decryptFirstMessage(incoming.Envelope, bobPublicPrekeys, bobSecretPrekeys) if err != nil { t.Fatal(err) } fmt.Printf("Bob hears: %s\n", out) msg2 := []byte("Envelope2") payload2 := proto.Message{ Subject: "Subject3", Participants: participants, Dename: bob, Contents: msg2, } envelope2, err := payload2.Marshal() if err != nil { t.Fatal(err) } err = bobConf.sendMessage(envelope2, alice, bobRatch) if err != nil { t.Fatal(err) } incomingAlice := <-aliceConnToServer.ReadEnvelope aliceConf.fillAuth = util.FillAuthWith((*[32]byte)(&aliceConf.MessageAuthSecretKey)) aliceConf.checkAuth = util.CheckAuthWith(aliceConf.ProfileRatchet) aliceRatchets, err := AllRatchets(aliceConf, aliceConf.fillAuth, aliceConf.checkAuth) outAlice, _, err := decryptMessage(incomingAlice.Envelope, aliceRatchets) if err != nil { t.Fatal(err) } ha := sha256.Sum256(incomingAlice.Envelope) if err := aliceConf.saveMessage(outAlice); err != nil { t.Fatal(err) } if err := util.DeleteMessages(aliceConnToServer, []*[32]byte{&ha}); err != nil { t.Fatal(err) } fmt.Printf("Alice hears: %s\n", outAlice) //TODO: Confirm message is as expected within the test }
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 }