From 0221e4e5b7bf2e52aa154dd878a52f72b6fffe8b Mon Sep 17 00:00:00 2001 From: Aaron Date: Mon, 28 Dec 2020 18:37:41 +0100 Subject: [PATCH] add support for uuids #1 --- attachments.go | 32 +++++++++ axolotl/session.go | 7 +- config.go | 3 +- contacts.go | 72 ++++++++++++------- crypto.go | 30 +++++++- go.mod | 2 +- groups.go | 33 +++++---- link.go | 4 +- server.go | 170 ++++++++++++++++++++++++++++++--------------- store.go | 5 +- sync.go | 5 +- textsecure.go | 146 +++++++++++++++++++++++++++++--------- transport.go | 2 +- websocket.go | 4 +- 14 files changed, 371 insertions(+), 144 deletions(-) diff --git a/attachments.go b/attachments.go index faa08cf..bd331f7 100644 --- a/attachments.go +++ b/attachments.go @@ -14,6 +14,7 @@ import ( "net/http" "strconv" + signalservice "github.com/signal-golang/textsecure/protobuf" textsecure "github.com/signal-golang/textsecure/protobuf" ) @@ -157,6 +158,37 @@ func handleSingleAttachment(a *textsecure.AttachmentPointer) (*Attachment, error return &Attachment{bytes.NewReader(b), a.GetContentType(), a.GetFileName()}, nil } +func handleProfileAvatar(profileAvatar *signalservice.ContactDetails_Avatar, key []byte) (*Attachment, error) { + + loc, err := getProfileLocation(profileAvatar.String()) + if err != nil { + return nil, err + } + r, err := getAttachment(loc) + if err != nil { + return nil, err + } + defer r.Close() + + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + l := len(b) - 16 + if !verifyMAC(key[16:], b[:l], b[l:]) { + return nil, ErrInvalidMACForAttachment + } + + b, err = aesDecrypt(key[:16], b[:l]) + if err != nil { + return nil, err + } + + // TODO: verify digest + + return &Attachment{bytes.NewReader(b), profileAvatar.GetContentType(), ""}, nil +} func handleAttachments(dm *textsecure.DataMessage) ([]*Attachment, error) { atts := dm.GetAttachments() diff --git a/axolotl/session.go b/axolotl/session.go index a3736d1..b11ff99 100644 --- a/axolotl/session.go +++ b/axolotl/session.go @@ -9,10 +9,11 @@ import ( "errors" "fmt" + "github.com/golang/protobuf/proto" protobuf "github.com/signal-golang/textsecure/axolotl/protobuf" "github.com/signal-golang/textsecure/curve25519sign" - "github.com/signal-golang/textsecure/protobuf" - "github.com/golang/protobuf/proto" + signalservice "github.com/signal-golang/textsecure/protobuf" + log "github.com/sirupsen/logrus" ) type sessionState struct { @@ -658,7 +659,7 @@ func (ss *sessionState) decrypt(ciphertext *WhisperMessage) ([]byte, error) { func (sc *SessionCipher) SessionDecryptWhisperMessage(ciphertext *WhisperMessage) ([]byte, error) { sc.SessionStore.Lock() defer sc.SessionStore.Unlock() - + log.Debugln("a", sc.RecipientID) sr, err := sc.SessionStore.LoadSession(sc.RecipientID, sc.DeviceID) if err != nil { return nil, err diff --git a/config.go b/config.go index 1166399..df24638 100644 --- a/config.go +++ b/config.go @@ -11,7 +11,8 @@ import ( // Config holds application configuration settings type Config struct { - Tel string `yaml:"tel"` // Our telephone number + Tel string `yaml:"tel"` // Our telephone number + UUID string `yaml:"uuid"` Server string `yaml:"server"` // The TextSecure server URL RootCA string `yaml:"rootCA"` // The TLS signing certificate of the server we connect to ProxyServer string `yaml:"proxy"` // HTTP Proxy URL if one is being used diff --git a/contacts.go b/contacts.go index 288c054..6b1f6a7 100644 --- a/contacts.go +++ b/contacts.go @@ -4,9 +4,11 @@ package textsecure import ( + "bytes" + "io" "io/ioutil" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" @@ -14,13 +16,19 @@ import ( // Contact contains information about a contact. type Contact struct { - Tel string - Uuid string - Name string - Color string - Avatar []byte - Blocked bool - ExpireTimer uint32 + UUID string + Tel string + ProfileKey []byte + IdentityKey []byte + Name string + Username string + Avatar []byte + Color string + Blocked bool + Verified *signalservice.Verified + ExpireTimer uint32 + InboxPosition uint32 + Archived bool } type yamlContacts struct { @@ -41,6 +49,7 @@ func loadContacts(contactsYaml *yamlContacts) { var filePath string +// ReadContacts loads the contacts yaml file and pareses it func ReadContacts(fileName string) ([]Contact, error) { b, err := ioutil.ReadFile(fileName) filePath = fileName @@ -66,6 +75,8 @@ func WriteContacts(filename string, contacts2 []Contact) error { } return ioutil.WriteFile(filename, b, 0600) } + +// WriteContactsToPath saves a list of contacts to a file at the standard location func WriteContactsToPath() error { c := contactsToYaml() b, err := yaml.Marshal(c) @@ -89,29 +100,36 @@ func contactsToYaml() *yamlContacts { func updateContact(c *signalservice.ContactDetails) error { log.Debugln("[textsecure] updateContact ", c.GetName()) - // var r io.Reader - // av := c.GetAvatar() - // buf := new(bytes.Buffer) - // if av != nil { - // att, err := handleSingleAttachment(av) - // if err != nil { - // return err - // } - // r = att.R - // buf.ReadFrom(r) - // } + var r io.Reader + av := c.GetAvatar() + buf := new(bytes.Buffer) + if av != nil { + att, err := handleProfileAvatar(av, c.GetProfileKey()) + if err != nil { + return err + } + r = att.R + buf.ReadFrom(r) + } + avatar, _ := ioutil.ReadAll(buf) contacts[c.GetNumber()] = Contact{ - Tel: c.GetNumber(), - Uuid: c.GetUuid(), - Name: c.GetName(), - Color: c.GetColor(), - Avatar: []byte(""), - Blocked: c.GetBlocked(), - ExpireTimer: c.GetExpireTimer(), + Tel: c.GetNumber(), + UUID: c.GetUuid(), + Name: c.GetName(), + Avatar: avatar, + Color: c.GetColor(), + Verified: c.GetVerified(), + ProfileKey: c.GetProfileKey(), + Blocked: c.GetBlocked(), + ExpireTimer: c.GetExpireTimer(), + InboxPosition: c.GetInboxPosition(), + Archived: c.GetArchived(), } + log.Debugln(c.GetAvatar(), buf) return WriteContactsToPath() } + func handleContacts(src string, dm *signalservice.DataMessage) ([]*signalservice.DataMessage_Contact, error) { cs := dm.GetContact() if cs == nil { @@ -145,6 +163,8 @@ func handleContacts(src string, dm *signalservice.DataMessage) ([]*signalservice return nil, nil } + +// RequestContactInfo sends func RequestContactInfo() error { var t signalservice.SyncMessage_Request_Type t = 1 diff --git a/crypto.go b/crypto.go index ead9716..5b98d4d 100644 --- a/crypto.go +++ b/crypto.go @@ -16,9 +16,10 @@ import ( "fmt" "io" - "github.com/signal-golang/textsecure/axolotl" - "github.com/signal-golang/textsecure/protobuf" "github.com/golang/protobuf/proto" + "github.com/signal-golang/textsecure/axolotl" + signalservice "github.com/signal-golang/textsecure/protobuf" + log "github.com/sirupsen/logrus" "golang.org/x/crypto/curve25519" ) @@ -87,6 +88,8 @@ func aesDecrypt(key, ciphertext []byte) ([]byte, error) { } if len(ciphertext)%aes.BlockSize != 0 { + length := len(ciphertext) % aes.BlockSize + log.Debugln("[textsecure] aesDecrypt ciphertext not multiple of AES blocksize", length) return nil, errors.New("ciphertext not multiple of AES blocksize") } @@ -100,6 +103,29 @@ func aesDecrypt(key, ciphertext []byte) ([]byte, error) { return ciphertext[aes.BlockSize : len(ciphertext)-int(pad)], nil } +func aesCtrNoPaddingDecrypt(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + if len(ciphertext)%aes.BlockSize != 0 { + length := len(ciphertext) % aes.BlockSize + log.Debugln("[textsecure] aesDecrypt ciphertext not multiple of AES blocksize", length) + return nil, errors.New("ciphertext not multiple of AES blocksize") + } + + iv := ciphertext[:aes.BlockSize] + mode := cipher.NewCBCDecrypter(block, iv) + // CryptBlocks can work in-place if the two arguments are the same. + mode.CryptBlocks(ciphertext, ciphertext) + // s := string(ciphertext[:]) + + return ciphertext, nil + + // return ciphertext[aes.BlockSize : len(ciphertext)-int(pad)], nil +} + // ProvisioningCipher func provisioningCipher(pm *signalservice.ProvisionMessage, theirPublicKey *axolotl.ECPublicKey) ([]byte, error) { ourKeyPair := axolotl.GenerateIdentityKeyPair() diff --git a/go.mod b/go.mod index c2f4c3f..384597d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect golang.org/x/text v0.3.2 - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/protobuf v1.25.0 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v2 v2.3.0 ) diff --git a/groups.go b/groups.go index 128907c..0ee5604 100644 --- a/groups.go +++ b/groups.go @@ -16,7 +16,7 @@ import ( log "github.com/sirupsen/logrus" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" "gopkg.in/yaml.v2" ) @@ -74,6 +74,8 @@ func loadGroup(path string) error { groups[hexid] = group return nil } + +// RemoveGroupKey removes the group key func RemoveGroupKey(hexid string) error { err := os.Remove(config.StorageDir + "/groups/" + hexid) if err != nil { @@ -163,7 +165,7 @@ func quitGroup(src string, hexid string) error { // GroupUpdateFlag signals that this message updates the group membership or name. var GroupUpdateFlag uint32 = 1 -// GroupLeavelag signals that this message is a group leave message +// GroupLeaveFlag signals that this message is a group leave message var GroupLeaveFlag uint32 = 2 // handleGroups is the main entry point for handling the group metadata on messages. @@ -232,7 +234,7 @@ func sendGroupHelper(hexid string, msg string, a *att, timer uint32) (uint64, er for _, m := range g.Members { if m != config.Tel { omsg := &outgoingMessage{ - tel: m, + destination: m, msg: msg, attachment: a, expireTimer: timer, @@ -246,10 +248,9 @@ func sendGroupHelper(hexid string, msg string, a *att, timer uint32) (uint64, er if err != nil { log.Errorln("[textsecure] sendGroupHelper", err, m) return 0, err - } else { - log.Debugln("[textsecure] sendGroupHelper message to group sent", m) - } + log.Debugln("[textsecure] sendGroupHelper message to group sent", m) + } } return ts, nil @@ -269,6 +270,8 @@ func SendGroupAttachment(hexid string, msg string, r io.Reader, timer uint32) (u } return sendGroupHelper(hexid, msg, a, timer) } + +// SendGroupVoiceNote sends an voice note to a group func SendGroupVoiceNote(hexid string, msg string, r io.Reader, timer uint32) (uint64, error) { ct, r := MIMETypeFromReader(r) a, err := uploadVoiceNote(r, ct) @@ -316,7 +319,7 @@ func sendUpdate(g *Group) error { for _, m := range g.Members { if m != config.Tel { omsg := &outgoingMessage{ - tel: m, + destination: m, group: &groupMessage{ id: g.ID, name: g.Name, @@ -347,13 +350,15 @@ func newGroup(name string, members []string) (*Group, error) { } return groups[hexid], nil } + +// RequestGroupInfo updates the info for the group like members or the avatat func RequestGroupInfo(g *Group) error { log.Debugln("[textsecure] request group update", g.Hexid) for _, m := range g.Members { if m != config.Tel { log.Debugln(m) omsg := &outgoingMessage{ - tel: m, + destination: m, group: &groupMessage{ id: g.ID, typ: signalservice.GroupContext_REQUEST_INFO, @@ -367,7 +372,7 @@ func RequestGroupInfo(g *Group) error { } if len(g.Members) == 0 { omsg := &outgoingMessage{ - tel: config.Tel, + destination: config.Tel, group: &groupMessage{ id: g.ID, typ: signalservice.GroupContext_REQUEST_INFO, @@ -409,10 +414,12 @@ func removeGroup(id []byte) error { } return nil } -func GetGroupById(hexId string) (*Group, error) { - g, ok := groups[hexId] + +// GetGroupById returns a group by it's id +func GetGroupById(hexID string) (*Group, error) { + g, ok := groups[hexID] if !ok { - return nil, UnknownGroupIDError{hexId} + return nil, UnknownGroupIDError{hexID} } return g, nil } @@ -427,7 +434,7 @@ func LeaveGroup(hexid string) error { for _, m := range g.Members { if m != config.Tel { omsg := &outgoingMessage{ - tel: m, + destination: m, group: &groupMessage{ id: g.ID, typ: signalservice.GroupContext_QUIT, diff --git a/link.go b/link.go index c8165e7..d1839e1 100644 --- a/link.go +++ b/link.go @@ -8,6 +8,7 @@ import ( var code2 string +// AddNewLinkedDevice adds a new signal desktop client func AddNewLinkedDevice(uuid string, publicKey string) error { log.Printf("AddNewLinkedDevice") if code2 == "" { @@ -17,9 +18,6 @@ func AddNewLinkedDevice(uuid string, publicKey string) error { } code2 = code } - // log.Printf("code: " + code2) - // log.Printf("uuid: " + uuid) - // log.Printf("publicKey: " + publicKey) err := addNewDevice(uuid, publicKey, code2) if err != nil { diff --git a/server.go b/server.go index f665d13..de5ee39 100644 --- a/server.go +++ b/server.go @@ -5,20 +5,20 @@ package textsecure import ( "bytes" - "crypto/aes" - "crypto/cipher" + "crypto/tls" "encoding/base64" "encoding/json" "errors" "fmt" "io" + "net/http" "strconv" "strings" "time" "github.com/golang/protobuf/proto" "github.com/signal-golang/textsecure/axolotl" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" log "github.com/sirupsen/logrus" ) @@ -173,17 +173,29 @@ func requestCode(tel, method string) (string, error) { // return "", nil } +// AccountAttributes describes what features are supported type AccountAttributes struct { - SignalingKey string `json:"signalingKey"` - RegistrationID uint32 `json:"registrationId"` - FetchesMessages bool `json:"fetchesMessages"` - Video bool `json:"video"` - Voice bool `json:"voice"` - Pin string `json:"pin"` - BasicStorageCredentials AuthCredentials `json:"basicStorageCredentials"` + SignalingKey string `json:"signalingKey"` + RegistrationID uint32 `json:"registrationId"` + FetchesMessages bool `json:"fetchesMessages"` + Video bool `json:"video"` + Voice bool `json:"voice"` + Pin string `json:"pin"` + BasicStorageCredentials AuthCredentials `json:"basicStorageCredentials"` + Capabilities AccountCapabilities `json:"capabilities"` // UnidentifiedAccessKey *byte `json:"unidentifiedAccessKey"` // UnrestrictedUnidentifiedAccess *bool `json:"unrestrictedUnidentifiedAccess"` } + +// AccountCapabilities describes what functions axolotl supports +type AccountCapabilities struct { + UUID bool `json:"uuid"` + Gv2 bool `json:"gv2"` + Storage bool `json:"storage"` + Gv1Migration bool `json:"gv1Migration"` +} + +// AuthCredentials holds the credentials for the websocket connection type AuthCredentials struct { Username string `json:"username"` Password string `json:"password"` @@ -193,6 +205,7 @@ type RegistrationLockFailure struct { Credentials AuthCredentials `json:"backupCredentials"` } +// verifyCode verificates the account with signal server func verifyCode(code string, pin *string, credentials *AuthCredentials) (error, *AuthCredentials) { code = strings.Replace(code, "-", "", -1) @@ -202,6 +215,12 @@ func verifyCode(code string, pin *string, credentials *AuthCredentials) (error, FetchesMessages: true, Voice: false, Video: false, + Capabilities: AccountCapabilities{ + UUID: true, + Gv2: true, + Storage: false, + Gv1Migration: false, + }, // Pin: nil, // UnidentifiedAccessKey: nil, // UnrestrictedUnidentifiedAccess: nil, @@ -234,7 +253,7 @@ func verifyCode(code string, pin *string, credentials *AuthCredentials) (error, if err != nil { return err, nil } - return errors.New(fmt.Sprintf("RegistrationLockFailure \n Time to wait \n %s", newStr)), &v.Credentials + return fmt.Errorf(fmt.Sprintf("RegistrationLockFailure \n Time to wait \n %s", newStr)), &v.Credentials } else { return resp, nil } @@ -356,18 +375,25 @@ func addNewDevice(ephemeralId, publicKey, verificationCode string) error { // profiles type Profile struct { - IdentityKey []byte `json:"identityKey"` + IdentityKey string `json:"identityKey"` Name string `json:"name"` Avatar string `json:"avatar"` UnidentifiedAccess string `json:"unidentifiedAccess"` UnrestrictedUnidentifiedAccess bool `json:"unrestrictedUnidentifiedAccess"` Capabilities ProfileSettings `json:"capabilities"` + Username string `json:"username"` + UUID string `json:"uuid"` + Payments string `json:"payments"` + Credential string `json:"credential"` } type ProfileSettings struct { - Uuid bool `json:"uuid"` + Uuid bool `json:"uuid"` + Gv2 bool `json:"gv2"` + Gv1Migration bool `json:"gv1-migration"` } -func GetProfile(tel string) (*Profile, error) { +// +func GetProfile(tel string) (Contact, error) { resp, err := transport.get(fmt.Sprintf(PROFILE_PATH, tel)) if err != nil { @@ -376,24 +402,25 @@ func GetProfile(tel string) (*Profile, error) { profile := &Profile{} dec := json.NewDecoder(resp.Body) - err = dec.Decode(&profile) if err != nil { log.Debugln(err) - } avatar, _ := GetAvatar(profile.Avatar) buf := new(bytes.Buffer) buf.ReadFrom(avatar) c := contacts[tel] - c.Avatar = decryptAvatar(buf.Bytes(), profile.IdentityKey) + avatarDecrypted, err := decryptAvatar(buf.Bytes(), []byte(profile.IdentityKey)) + if err != nil { + log.Debugln(err) + } + c.Username = profile.Username + c.UUID = profile.UUID + c.Avatar = avatarDecrypted contacts[tel] = c WriteContactsToPath() - - // - // return devices.DeviceList, nil - return profile, nil + return c, nil } var cdnTransport *httpTransporter @@ -404,36 +431,55 @@ func setupCDNTransporter() { } func GetAvatar(avatarUrl string) (io.ReadCloser, error) { - log.Debugln(SIGNAL_CDN_URL + "/" + avatarUrl) - resp, err := cdnTransport.get("/" + avatarUrl) + log.Debugln("[a]" + SIGNAL_CDN_URL + "/" + avatarUrl) + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + c := &http.Client{Transport: customTransport} + req, err := http.NewRequest("GET", SIGNAL_CDN_URL+"/"+avatarUrl, nil) + req.Header.Add("Host", SERVICE_REFLECTOR_HOST) + req.Header.Add("Content-Type", "application/octet-stream") + resp, err := c.Do(req) if err != nil { log.Debugln("[textsecure] getAvatar ", err) + return nil, err } + return resp.Body, nil } -func decryptAvatar(avatar []byte, identityKey []byte) []byte { - block, err := aes.NewCipher(identityKey[:32]) - log.Debugln("-0", avatar[:12]) - if err != nil { - log.Debugln("0", err) - - return nil - } - // nonce := []byte{} - aesgcm, err := cipher.NewGCM(block) - log.Debugln("-0", aesgcm.NonceSize()) +func decryptAvatar(avatar []byte, identityKey []byte) ([]byte, error) { + l := len(avatar[:]) - 30 + // if !verifyMAC(identityKey[16:], avatar[:l], avatar[l:]) { + // return nil, ErrInvalidMACForAttachment + // } + b, err := aesCtrNoPaddingDecrypt(identityKey[:16], avatar[:l]) if err != nil { - log.Debugln("1", err) - } - b, err := aesgcm.Open(nil, avatar[:12], avatar, nil) - if err != nil { - log.Debugln("3", err) + return nil, err } - return b + return b, nil + + // block, err := aes.NewCipher(identityKey[:32]) + // log.Debugln("-0", avatar[:12]) + // if err != nil { + // log.Debugln("0", err) + + // return nil, err + // } + // // nonce := []byte{} + // aesgcm, err := cipher.NewGCM(block) + // log.Debugln("-0", aesgcm.NonceSize()) + + // if err != nil { + // log.Debugln("1", err) + // } + // b, err := aesgcm.Open(nil, avatar[:12], avatar, nil) + // if err != nil { + // log.Debugln("3", err) + // } + // return b } func generateNonce(avatar []byte, length int) []byte { var offset int @@ -527,6 +573,11 @@ func GetRegisteredContacts() ([]Contact, error) { return nil, resp } dec := json.NewDecoder(resp.Body) + buf := new(bytes.Buffer) + buf.ReadFrom(resp.Body) + newStr := buf.String() + + fmt.Printf(newStr) var jc map[string][]jsonContact dec.Decode(&jc) @@ -568,6 +619,11 @@ func getAttachmentLocation(id uint64, key string, cdnNumber uint32) (string, err } } +func getProfileLocation(profilePath string) (string, error) { + cdn := SIGNAL_CDN_URL + return cdn + fmt.Sprintf(profilePath), nil +} + // Messages type jsonMessage struct { @@ -616,6 +672,9 @@ func createMessage(msg *outgoingMessage) *signalservice.DataMessage { MembersE164: msg.group.members, } } + if msg.groupV2 != nil { + dm.GroupV2 = msg.groupV2 + } dm.Flags = &msg.flags @@ -756,8 +815,8 @@ var ErrRemoteGone = errors.New("the remote device is gone (probably reinstalled) var deviceLists = map[string][]uint32{} -func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestamp *uint64) (*sendMessageResponse, error) { - bm, err := buildMessage(tel, paddedMessage, deviceLists[tel], isSync) +func buildAndSendMessage(uuid string, paddedMessage []byte, isSync bool, timestamp *uint64) (*sendMessageResponse, error) { + bm, err := buildMessage(uuid, paddedMessage, deviceLists[uuid], isSync) if err != nil { return nil, err } @@ -768,12 +827,12 @@ func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestam timestamp = &now } m["timestamp"] = timestamp - m["destination"] = tel + m["destination"] = uuid body, err := json.Marshal(m) if err != nil { return nil, err } - resp, err := transport.putJSON(fmt.Sprintf(MESSAGE_PATH, tel), body) + resp, err := transport.putJSON(fmt.Sprintf(MESSAGE_PATH, uuid), body) if err != nil { return nil, err } @@ -784,7 +843,7 @@ func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestam dec.Decode(&j) log.Debugf("[textsecure] Mismatched devices: %+v\n", j) devs := []uint32{} - for _, id := range deviceLists[tel] { + for _, id := range deviceLists[uuid] { in := true for _, eid := range j.ExtraDevices { if id == eid { @@ -796,8 +855,8 @@ func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestam devs = append(devs, id) } } - deviceLists[tel] = append(devs, j.MissingDevices...) - return buildAndSendMessage(tel, paddedMessage, isSync, timestamp) + deviceLists[uuid] = append(devs, j.MissingDevices...) + return buildAndSendMessage(uuid, paddedMessage, isSync, timestamp) } if resp.Status == staleDevicesStatus { dec := json.NewDecoder(resp.Body) @@ -805,9 +864,9 @@ func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestam dec.Decode(&j) log.Debugf("[textsecure] Stale devices: %+v\n", j) for _, id := range j.StaleDevices { - textSecureStore.DeleteSession(recID(tel), id) + textSecureStore.DeleteSession(recID(uuid), id) } - return buildAndSendMessage(tel, paddedMessage, isSync, timestamp) + return buildAndSendMessage(uuid, paddedMessage, isSync, timestamp) } if resp.isError() { return nil, resp @@ -823,8 +882,8 @@ func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool, timestam } func sendMessage(msg *outgoingMessage) (uint64, error) { - if _, ok := deviceLists[msg.tel]; !ok { - deviceLists[msg.tel] = []uint32{1} + if _, ok := deviceLists[msg.destination]; !ok { + deviceLists[msg.destination] = []uint32{1} } dm := createMessage(msg) @@ -837,16 +896,16 @@ func sendMessage(msg *outgoingMessage) (uint64, error) { return 0, err } - resp, err := buildAndSendMessage(msg.tel, padMessage(b), false, dm.Timestamp) + resp, err := buildAndSendMessage(msg.destination, padMessage(b), false, dm.Timestamp) if err != nil { return 0, err } if resp.NeedsSync { - log.Debugf("[textsecure] Needs sync. destination: %s", msg.tel) + log.Debugf("[textsecure] Needs sync. destination: %s", msg.destination) sm := &signalservice.SyncMessage{ Sent: &signalservice.SyncMessage_Sent{ - DestinationE164: &msg.tel, + DestinationE164: &msg.destination, Timestamp: dm.Timestamp, Message: dm, }, @@ -856,7 +915,7 @@ func sendMessage(msg *outgoingMessage) (uint64, error) { if serr != nil { log.WithFields(log.Fields{ "error": serr, - "destination": msg.tel, + "destination": msg.destination, "timestamp": resp.Timestamp, }).Error("Failed to send sync message") } @@ -866,6 +925,7 @@ func sendMessage(msg *outgoingMessage) (uint64, error) { } func sendSyncMessage(sm *signalservice.SyncMessage, timestamp *uint64) (uint64, error) { + log.Debugln("[textsecure] sendSyncMessage", sm.Request.Type) if _, ok := deviceLists[config.Tel]; !ok { deviceLists[config.Tel] = []uint32{1} } diff --git a/store.go b/store.go index 2d6b87a..dc133f3 100644 --- a/store.go +++ b/store.go @@ -258,17 +258,18 @@ func ContactIdentityKey(id string) ([]byte, error) { } func GetFingerprint(remoteIdentifier string) ([]string, []byte, error) { + localIdentifier := config.Tel localIdentityKey := MyIdentityKey() remoteIdentityKey, err := ContactIdentityKey(remoteIdentifier) if err != nil { - return nil, nil, err + return nil, nil, err } numericFingerprint, scannableFingerprint, err := fingerprint.CreateFingerprintSimple(1, localIdentifier, localIdentityKey, remoteIdentifier, remoteIdentityKey) if err != nil { - return nil, nil, err + return nil, nil, err } return numericFingerprint, scannableFingerprint, nil } diff --git a/sync.go b/sync.go index b6005ea..c7633f9 100644 --- a/sync.go +++ b/sync.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/golang/protobuf/proto" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" log "github.com/sirupsen/logrus" ) @@ -135,9 +135,10 @@ func sendContactUpdate() error { for _, c := range lc { cd := &signalservice.ContactDetails{ Number: &c.Tel, - Uuid: &c.Uuid, + Uuid: &c.UUID, Name: &c.Name, Color: &c.Color, + Verified: c.Verified, Blocked: &c.Blocked, ExpireTimer: &c.ExpireTimer, diff --git a/textsecure.go b/textsecure.go index a208672..665b381 100644 --- a/textsecure.go +++ b/textsecure.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strings" "bytes" @@ -19,7 +20,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/signal-golang/textsecure/axolotl" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" log "github.com/sirupsen/logrus" ) @@ -103,9 +104,10 @@ type att struct { } type outgoingMessage struct { - tel string + destination string msg string group *groupMessage + groupV2 *signalservice.GroupContextV2 attachment *att flags uint32 expireTimer uint32 @@ -128,20 +130,21 @@ func NewDeviceVerificationCode() (string, error) { } // AddDevice links a new device -func AddDevice(ephemeralId, publicKey, verificationCode string) error { - return addNewDevice(ephemeralId, publicKey, verificationCode) +func AddDevice(ephemeralID, publicKey, verificationCode string) error { + return addNewDevice(ephemeralID, publicKey, verificationCode) } // SendMessage sends the given text message to the given contact. -func SendMessage(tel, msg string, timer uint32) (uint64, error) { +func SendMessage(uuid, msg string, timer uint32) (uint64, error) { omsg := &outgoingMessage{ - tel: tel, + destination: uuid, msg: msg, expireTimer: timer, } return sendMessage(omsg) } +// MIMETypeFromReader returns the mime type that is inside the reader func MIMETypeFromReader(r io.Reader) (mime string, reader io.Reader) { var buf bytes.Buffer io.CopyN(&buf, r, 1024) @@ -151,28 +154,30 @@ func MIMETypeFromReader(r io.Reader) (mime string, reader io.Reader) { // SendAttachment sends the contents of a reader, along // with an optional message to a given contact. -func SendAttachment(tel, msg string, r io.Reader, timer uint32) (uint64, error) { +func SendAttachment(uuid string, msg string, r io.Reader, timer uint32) (uint64, error) { ct, r := MIMETypeFromReader(r) a, err := uploadAttachment(r, ct) if err != nil { return 0, err } omsg := &outgoingMessage{ - tel: tel, + destination: uuid, msg: msg, attachment: a, expireTimer: timer, } return sendMessage(omsg) } -func SendVoiceNote(tel, msg string, r io.Reader, timer uint32) (uint64, error) { + +// SendVoiceNote sends a voice note +func SendVoiceNote(uuid, msg string, r io.Reader, timer uint32) (uint64, error) { ct, r := MIMETypeFromReader(r) a, err := uploadVoiceNote(r, ct) if err != nil { return 0, err } omsg := &outgoingMessage{ - tel: tel, + destination: uuid, msg: msg, attachment: a, expireTimer: timer, @@ -181,17 +186,17 @@ func SendVoiceNote(tel, msg string, r io.Reader, timer uint32) (uint64, error) { } // EndSession terminates the session with the given peer. -func EndSession(tel string, msg string) (uint64, error) { +func EndSession(uuid string, msg string) (uint64, error) { omsg := &outgoingMessage{ - tel: tel, - msg: msg, - flags: uint32(signalservice.DataMessage_END_SESSION), + destination: uuid, + msg: msg, + flags: uint32(signalservice.DataMessage_END_SESSION), } ts, err := sendMessage(omsg) if err != nil { return 0, err } - textSecureStore.DeleteAllSessions(recID(tel)) + textSecureStore.DeleteAllSessions(recID(uuid)) return ts, nil } @@ -205,6 +210,7 @@ type Attachment struct { // Message represents a message received from the peer. // It can optionally include attachments and be sent to a group. type Message struct { + sourceUUID string source string message string attachments []*Attachment @@ -227,6 +233,16 @@ func (m *Message) Source() string { return m.source } +// SourceUUID returns the UUID of the sender of the message. +func (m *Message) SourceUUID() string { + return m.sourceUUID +} + +// ChatID returns the ChatID of the sender of the message. +func (m *Message) ChatID() string { + return m.sourceUUID +} + // Message returns the message body. func (m *Message) Message() string { return m.message @@ -252,21 +268,27 @@ func (m *Message) Flags() uint32 { return m.flags } +// ExpireTimer returns the expire timer in the message func (m *Message) ExpireTimer() uint32 { return m.expireTimer } +// Sticker returns the sticker in the message func (m *Message) Sticker() *signalservice.DataMessage_Sticker { return m.sticker } +// Contact returns the contact in the message func (m *Message) Contact() []*signalservice.DataMessage_Contact { return m.contact } +// Quote returns the quote in the message func (m *Message) Quote() *signalservice.DataMessage_Quote { return m.quote } + +// Reaction returns the reaction in the message func (m *Message) Reaction() *signalservice.DataMessage_Reaction { return m.reaction } @@ -432,15 +454,16 @@ func registerDevice() error { func handleReceipt(env *signalservice.Envelope) { if client.ReceiptHandler != nil { - client.ReceiptHandler(env.GetSourceE164(), env.GetSourceDevice(), env.GetTimestamp()) + client.ReceiptHandler(env.GetSourceUuid(), env.GetSourceDevice(), env.GetTimestamp()) } } +// recID removes the + from phone numbers func recID(source string) string { return source[1:] } -func handleMessage(src string, timestamp uint64, b []byte) error { +func handleMessage(src string, srcUUID string, timestamp uint64, b []byte) error { b = stripPadding(b) content := &signalservice.Content{} @@ -450,7 +473,7 @@ func handleMessage(src string, timestamp uint64, b []byte) error { } if dm := content.GetDataMessage(); dm != nil { - return handleDataMessage(src, timestamp, dm) + return handleDataMessage(src, srcUUID, timestamp, dm) } else if sm := content.GetSyncMessage(); sm != nil && config.Tel == src { return handleSyncMessage(src, timestamp, sm) } else if cm := content.GetCallMessage(); cm != nil { @@ -463,7 +486,7 @@ func handleMessage(src string, timestamp uint64, b []byte) error { //FIXME get the right content // log.Errorf(content) - log.Errorln("Unknown message content received", content) + log.Errorln("[textsecure] Unknown message content received", content) return nil } @@ -480,7 +503,7 @@ func handleFlags(src string, dm *signalservice.DataMessage) (uint32, error) { } // handleDataMessage handles an incoming DataMessage and calls client callbacks -func handleDataMessage(src string, timestamp uint64, dm *signalservice.DataMessage) error { +func handleDataMessage(src string, srcUUID string, timestamp uint64, dm *signalservice.DataMessage) error { flags, err := handleFlags(src, dm) if err != nil { return err @@ -490,13 +513,13 @@ func handleDataMessage(src string, timestamp uint64, dm *signalservice.DataMessa if err != nil { return err } - log.Debugln("[textsecure] handleDataMessage") - log.Debugln("[textsecure] handleDataMessage timestamps", timestamp, *dm.Timestamp, dm.GetExpireTimer()) + log.Debugln("[textsecure] handleDataMessage", timestamp, *dm.Timestamp, dm.GetExpireTimer()) gr, err := handleGroups(src, dm) if err != nil { return err } msg := &Message{ + sourceUUID: srcUUID, source: src, message: dm.GetBody(), attachments: atts, @@ -576,6 +599,48 @@ func (err MessageTypeNotImplementedError) Error() string { // ErrInvalidMACForMessage signals an incoming message with invalid MAC. var ErrInvalidMACForMessage = errors.New("invalid MAC for incoming message") +var currentSessionToRenameUUID string +var currentSessionToRenameE164 string + +func renameSession(path string, f os.FileInfo, err error) error { + if name := f.Name(); strings.HasPrefix(name, currentSessionToRenameE164) { + log.Debugln("bliub", name) + dir := filepath.Dir(path) + newname := strings.Replace(name, + currentSessionToRenameE164, + currentSessionToRenameUUID, 1) + newpath := filepath.Join(dir, newname) + err := os.Rename(path, newpath) + if err != nil { + return err + } + return nil + } + return nil +} + +// migrateKeyToUUID migrate keys base on phone numbers to uuids +func migrateKeyToUUID(session *axolotl.SessionCipher, e164 string, uuid string) error { + log.Infoln("[textsecure] migrate key for ", uuid) + oldIdentity := config.StorageDir + "/identity/remote_" + e164 + newIdentity := config.StorageDir + "/identity/remote_" + uuid + if _, err := os.Stat(oldIdentity); err == nil { + err := os.Rename(oldIdentity, newIdentity) + if err != nil { + // return err + log.Errorln("[textsecure] migrate key ", err) + } + } + log.Infoln("[textsecure] migrate sessions for ", uuid) + currentSessionToRenameUUID = uuid + currentSessionToRenameE164 = e164 + err := filepath.Walk(config.StorageDir+"/sessions", renameSession) + if err != nil { + return err + } + return nil +} + // Authenticate and decrypt a received message func handleReceivedMessage(msg []byte) error { macpos := len(msg) - 10 @@ -595,7 +660,8 @@ func handleReceivedMessage(msg []byte) error { if err != nil { return err } - recid := recID(env.GetSourceE164()) + recid := env.GetSourceUuid() + sc := axolotl.NewSessionCipher(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, env.GetSourceDevice()) switch *env.Type { case signalservice.Envelope_RECEIPT: @@ -604,25 +670,41 @@ func handleReceivedMessage(msg []byte) error { case signalservice.Envelope_CIPHERTEXT: msg := env.GetContent() if msg == nil { - return errors.New("Legacy messages unsupported") + return errors.New("[textsecure] Legacy messages unsupported") } wm, err := axolotl.LoadWhisperMessage(msg) if err != nil { + log.Infof("[textsecure] Incoming WhisperMessage %s.\n", err) return err } b, err := sc.SessionDecryptWhisperMessage(wm) if _, ok := err.(axolotl.DuplicateMessageError); ok { - log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) + log.Infof("[textsecure] Incoming WhisperMessage %s. Ignoring.\n", err) return nil } if _, ok := err.(axolotl.InvalidMessageError); ok { - log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) - return nil + // try the legacy way + log.Infof("[textsecure] Incoming WhisperMessage try legacy decrypting") + + recid := recID(env.GetSourceE164()) + sc := axolotl.NewSessionCipher(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, env.GetSourceDevice()) + b, err = sc.SessionDecryptWhisperMessage(wm) + if _, ok := err.(axolotl.DuplicateMessageError); ok { + log.Infof("[textsecure] Incoming WhisperMessage %s. Ignoring.\n", err) + return nil + } + haveToResetMsg := &Message{ + source: config.Tel, + message: "needs to reset encryption", + timestamp: env.GetTimestamp(), + flags: 99, + } + go client.MessageHandler(haveToResetMsg) } if err != nil { return err } - err = handleMessage(env.GetSourceE164(), env.GetTimestamp(), b) + err = handleMessage(env.GetSourceE164(), env.GetSourceUuid(), env.GetTimestamp(), b) if err != nil { return err } @@ -635,21 +717,21 @@ func handleReceivedMessage(msg []byte) error { } b, err := sc.SessionDecryptPreKeyWhisperMessage(pkwm) if _, ok := err.(axolotl.DuplicateMessageError); ok { - log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) + log.Infof("[textsecure] Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) return nil } if _, ok := err.(axolotl.PreKeyNotFoundError); ok { - log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) + log.Infof("[textsecure] Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) return nil } if _, ok := err.(axolotl.InvalidMessageError); ok { - log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) + log.Infof("[textsecure] Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) return nil } if err != nil { return err } - err = handleMessage(env.GetSourceE164(), env.GetTimestamp(), b) + err = handleMessage(env.GetSourceE164(), env.GetSourceUuid(), env.GetTimestamp(), b) if err != nil { return err } diff --git a/transport.go b/transport.go index f47fa91..96ff535 100644 --- a/transport.go +++ b/transport.go @@ -101,7 +101,7 @@ func (ht *httpTransporter) get(url string) (*response, error) { r.Body = resp.Body } - log.Debugf("GET %s %d\n", url, r.Status) + log.Debugf("[textsecure] GET %s %d\n", url, r.Status) return r, err } diff --git a/websocket.go b/websocket.go index 774e069..e340773 100644 --- a/websocket.go +++ b/websocket.go @@ -3,7 +3,6 @@ package textsecure import ( "crypto/tls" "errors" - "fmt" "net" "net/url" "strings" @@ -11,7 +10,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/gorilla/websocket" - "github.com/signal-golang/textsecure/protobuf" + signalservice "github.com/signal-golang/textsecure/protobuf" log "github.com/sirupsen/logrus" ) @@ -210,7 +209,6 @@ func StartListening() error { } - return fmt.Errorf("[textsecure] Connection closed") } // ErrNotListening is returned when trying to stop listening when there's no