├── COPYING ├── README.md ├── attachments.go ├── axolotl ├── crypto.go ├── crypto_test.go ├── curves_test.go ├── keys.go ├── keys_test.go ├── messages.go ├── messages_test.go ├── prekeys.go ├── prekeys_test.go ├── protobuf │ ├── LocalStorageProtocol.pb.go │ ├── LocalStorageProtocol.proto │ ├── WhisperTextProtocol.pb.go │ └── WhisperTextProtocol.proto ├── ratchet.go ├── session.go └── store.go ├── cmd └── textsecure │ ├── .config │ ├── config.yml │ └── contacts.yml │ └── main.go ├── config.go ├── contacts.go ├── crypto.go ├── crypto_test.go ├── curve25519sign ├── curve25519sign.go └── curve25519sign_test.go ├── groups.go ├── prekeys.go ├── protobuf ├── Provisioning.pb.go ├── Provisioning.proto ├── TextSecure.pb.go ├── TextSecure.proto ├── WebSocketResources.pb.go └── WebSocketResources.proto ├── rootca.go ├── server.go ├── store.go ├── sync.go ├── textsecure.go ├── transport.go └── websocket.go /README.md: -------------------------------------------------------------------------------- 1 | # TextSecure library and command line test client for Go 2 | 3 | This is a Go package implementing the TextSecure **push data** (i.e. not encrypted SMS) protocol v3 including the Axolotl ratchet. 4 | 5 | The included sample command line app can send and receive text messages and attachments and supports group chat. 6 | 7 | **The API presented by the package is in flux**, mainly driven by the needs of https://github.com/janimo/textsecure-qml 8 | 9 | Automatically generated documentation can be found on [GoDoc] (https://godoc.org/github.com/janimo/textsecure) 10 | 11 | Installation 12 | ------------ 13 | 14 | This command will install both the library and the test client. 15 | 16 | go get github.com/janimo/textsecure/cmd/textsecure 17 | 18 | For more details, including setting up Go, check the [wiki] (https://github.com/janimo/textsecure/wiki/Installation) 19 | 20 | Configuration 21 | ------------- 22 | 23 | Copy cmd/textsecure/.config to a directory and modify it, then run the tool from that directory. 24 | It will create .storage to hold all the protocol state. Removing that dir and running the tool again will trigger a reregistration with the server. 25 | 26 | Usage 27 | ----- 28 | 29 | **Do not run multiple instances of the app from the same directory, it (and the server) can get confused** 30 | 31 | This will show the supported command line flags 32 | 33 | textsecure -h 34 | 35 | Running the command without arguments will put it in receiving mode, and once it receives a message it will be able to talk to that contact. 36 | 37 | Discussions 38 | ----------- 39 | 40 | User and developer discussions happen on the [mailing list] (https://groups.google.com/forum/#!forum/textsecure-go) 41 | -------------------------------------------------------------------------------- /attachments.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "strconv" 14 | 15 | "github.com/janimo/textsecure/protobuf" 16 | ) 17 | 18 | // getAttachment downloads an encrypted attachment blob from the given URL 19 | func getAttachment(url string) (io.ReadCloser, error) { 20 | req, err := http.NewRequest("GET", url, nil) 21 | req.Header.Add("Content-type", "application/octet-stream") 22 | resp, err := http.DefaultClient.Do(req) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return resp.Body, nil 28 | } 29 | 30 | // putAttachment uploads an encrypted attachment to the given URL 31 | func putAttachment(url string, body []byte) error { 32 | br := bytes.NewReader(body) 33 | req, err := http.NewRequest("PUT", url, br) 34 | if err != nil { 35 | return err 36 | } 37 | req.Header.Add("Content-type", "application/octet-stream") 38 | req.Header.Add("Content-length", strconv.Itoa(len(body))) 39 | resp, err := http.DefaultClient.Do(req) 40 | 41 | if resp != nil && (resp.StatusCode < 200 || resp.StatusCode >= 300) { 42 | return fmt.Errorf("HTTP status %d\n", resp.StatusCode) 43 | } 44 | 45 | return err 46 | } 47 | 48 | // uploadAttachment encrypts, authenticates and uploads a given attachment to a location requested from the server 49 | func uploadAttachment(r io.Reader, ct string) (*att, error) { 50 | //combined AES-256 and HMAC-SHA256 key 51 | keys := make([]byte, 64) 52 | randBytes(keys) 53 | 54 | b, err := ioutil.ReadAll(r) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | e, err := aesEncrypt(keys[:32], b) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | m := appendMAC(keys[32:], e) 65 | 66 | id, location, err := allocateAttachment() 67 | if err != nil { 68 | return nil, err 69 | } 70 | err = putAttachment(location, m) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return &att{id, ct, keys}, nil 75 | } 76 | 77 | // ErrInvalidMACForAttachment signals that the downloaded attachment has an invalid MAC. 78 | var ErrInvalidMACForAttachment = errors.New("invalid MAC for attachment") 79 | 80 | func handleSingleAttachment(a *textsecure.AttachmentPointer) (*Attachment, error) { 81 | loc, err := getAttachmentLocation(*a.Id) 82 | if err != nil { 83 | return nil, err 84 | } 85 | r, err := getAttachment(loc) 86 | if err != nil { 87 | return nil, err 88 | } 89 | defer r.Close() 90 | 91 | b, err := ioutil.ReadAll(r) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | l := len(b) - 32 97 | if !verifyMAC(a.Key[32:], b[:l], b[l:]) { 98 | return nil, ErrInvalidMACForAttachment 99 | } 100 | 101 | b, err = aesDecrypt(a.Key[:32], b[:l]) 102 | if err != nil { 103 | return nil, err 104 | } 105 | return &Attachment{bytes.NewReader(b), a.GetContentType()}, nil 106 | } 107 | 108 | func handleAttachments(dm *textsecure.DataMessage) ([]*Attachment, error) { 109 | atts := dm.GetAttachments() 110 | if atts == nil { 111 | return nil, nil 112 | } 113 | 114 | all := make([]*Attachment, len(atts)) 115 | var err error 116 | for i, a := range atts { 117 | all[i], err = handleSingleAttachment(a) 118 | if err != nil { 119 | return nil, err 120 | } 121 | } 122 | return all, nil 123 | } 124 | -------------------------------------------------------------------------------- /axolotl/crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "bytes" 8 | "crypto/aes" 9 | "crypto/cipher" 10 | "crypto/hmac" 11 | "crypto/rand" 12 | "crypto/sha256" 13 | "errors" 14 | "fmt" 15 | "io" 16 | ) 17 | 18 | func randBytes(data []byte) { 19 | if _, err := io.ReadFull(rand.Reader, data); err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | // Decrypt returns the AES-CBC decryption of a ciphertext under a given key. 25 | func Decrypt(key, ciphertext []byte) ([]byte, error) { 26 | block, err := aes.NewCipher(key) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | if len(ciphertext)%aes.BlockSize != 0 { 32 | return nil, errors.New("not multiple of AES blocksize") 33 | } 34 | 35 | iv := ciphertext[:aes.BlockSize] 36 | mode := cipher.NewCBCDecrypter(block, iv) 37 | mode.CryptBlocks(ciphertext, ciphertext) 38 | pad := ciphertext[len(ciphertext)-1] 39 | if pad > aes.BlockSize { 40 | return nil, fmt.Errorf("pad value (%d) larger than AES blocksize (%d)", pad, aes.BlockSize) 41 | } 42 | return ciphertext[aes.BlockSize : len(ciphertext)-int(pad)], nil 43 | } 44 | 45 | // Encrypt returns the AES-CBC encryption of a plaintext under a given key. 46 | func Encrypt(key, iv, plaintext []byte) ([]byte, error) { 47 | block, err := aes.NewCipher(key) 48 | if err != nil { 49 | return nil, err 50 | } 51 | pad := aes.BlockSize - len(plaintext)%aes.BlockSize 52 | plaintext = append(plaintext, bytes.Repeat([]byte{byte(pad)}, pad)...) 53 | ciphertext := make([]byte, len(plaintext)) 54 | mode := cipher.NewCBCEncrypter(block, iv) 55 | mode.CryptBlocks(ciphertext, plaintext) 56 | return ciphertext, nil 57 | } 58 | 59 | // ValidTruncMAC checks whether a message is correctly authenticated using HMAC-SHA256. 60 | func ValidTruncMAC(msg, expectedMAC, key []byte) bool { 61 | actualMAC := ComputeTruncatedMAC(msg, key, len(expectedMAC)) 62 | return hmac.Equal(actualMAC, expectedMAC) 63 | } 64 | 65 | // ComputeTruncatedMAC computes a HMAC-SHA256 MAC and returns its prefix of a given size. 66 | func ComputeTruncatedMAC(msg, key []byte, size int) []byte { 67 | m := hmac.New(sha256.New, key) 68 | m.Write(msg) 69 | return m.Sum(nil)[:size] 70 | } 71 | -------------------------------------------------------------------------------- /axolotl/crypto_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "encoding/hex" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func hexToBytes(h string) []byte { 14 | b, err := hex.DecodeString(h) 15 | if err != nil { 16 | return nil 17 | } 18 | return b 19 | } 20 | 21 | var ( 22 | key = hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") 23 | iv = hexToBytes("000102030405060708090a0b0c0d0e0f") 24 | plaintext = hexToBytes("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710") 25 | ciphertext = hexToBytes("f58c4c04d6e5f1ba779eabfb5f7bfbd69cfc4e967edb808d679f777bc6702c7d39f23369a9d9bacfa530e26304231461b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644") 26 | ) 27 | 28 | func TestEncrypt(t *testing.T) { 29 | c, _ := Encrypt(key, iv, plaintext) 30 | assert.Equal(t, ciphertext, c, "Encrypted ciphertext must match") 31 | } 32 | 33 | func TestDecrypt(t *testing.T) { 34 | p, _ := Decrypt(key, append(iv, ciphertext...)) 35 | assert.Equal(t, plaintext, p, "Decrypted plaintext must match") 36 | } 37 | -------------------------------------------------------------------------------- /axolotl/curves_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "crypto/rand" 8 | "testing" 9 | 10 | "github.com/agl/ed25519" 11 | "github.com/agl/ed25519/extra25519" 12 | "github.com/stretchr/testify/assert" 13 | "golang.org/x/crypto/curve25519" 14 | ) 15 | 16 | func TestKeys(t *testing.T) { 17 | var cpriv, cpub, cpub2 [32]byte 18 | pub, priv, err := ed25519.GenerateKey(rand.Reader) 19 | if assert.NoError(t, err) { 20 | assert.True(t, extra25519.PublicKeyToCurve25519(&cpub, pub), "Calling PublicKeyToCurve25519 failed") 21 | extra25519.PrivateKeyToCurve25519(&cpriv, priv) 22 | curve25519.ScalarBaseMult(&cpub2, &cpriv) 23 | assert.Equal(t, cpub, cpub2) 24 | } 25 | } 26 | 27 | func TestKeyPairs(t *testing.T) { 28 | var pubkey, privkey [32]byte 29 | ikp := GenerateIdentityKeyPair() 30 | copy(privkey[:], ikp.PrivateKey.Key()[:]) 31 | curve25519.ScalarBaseMult(&pubkey, &privkey) 32 | assert.Equal(t, pubkey[:], ikp.PublicKey.Key()[:]) 33 | } 34 | -------------------------------------------------------------------------------- /axolotl/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "fmt" 8 | 9 | "golang.org/x/crypto/curve25519" 10 | ) 11 | 12 | // ECPrivateKey represents a 256 bit Curve25519 private key. 13 | type ECPrivateKey struct { 14 | key [32]byte 15 | } 16 | 17 | // ECPublicKey represents a 256 bit Curve25519 public key. 18 | type ECPublicKey struct { 19 | key [32]byte 20 | } 21 | 22 | const djbType = 5 23 | 24 | func ensureKeyLength(key []byte) { 25 | if len(key) != 32 { 26 | panic(fmt.Sprintf("Key length is not 32 but %d\n", len(key))) 27 | } 28 | } 29 | 30 | // NewECPrivateKey initializes a private key with the given value. 31 | func NewECPrivateKey(b []byte) *ECPrivateKey { 32 | ensureKeyLength(b) 33 | k := &ECPrivateKey{} 34 | copy(k.key[:], b) 35 | return k 36 | } 37 | 38 | // Key returns the value of the private key. 39 | func (k *ECPrivateKey) Key() *[32]byte { 40 | return &k.key 41 | } 42 | 43 | // NewECPublicKey initializes a public key with the given value. 44 | func NewECPublicKey(b []byte) *ECPublicKey { 45 | ensureKeyLength(b) 46 | k := &ECPublicKey{} 47 | copy(k.key[:], b) 48 | return k 49 | } 50 | 51 | // Key returns the value of the public key. 52 | func (k *ECPublicKey) Key() *[32]byte { 53 | return &k.key 54 | } 55 | 56 | // Serialize returns the public key prepended by the byte value 5, 57 | // as used in the TextSecure network protocol. 58 | func (k *ECPublicKey) Serialize() []byte { 59 | return append([]byte{djbType}, k.key[:]...) 60 | } 61 | 62 | // ECKeyPair represents a public and private key pair. 63 | type ECKeyPair struct { 64 | PrivateKey ECPrivateKey 65 | PublicKey ECPublicKey 66 | } 67 | 68 | // NewECKeyPair creates a key pair 69 | func NewECKeyPair() *ECKeyPair { 70 | privateKey := ECPrivateKey{} 71 | randBytes(privateKey.key[:]) 72 | 73 | privateKey.key[0] &= 248 74 | privateKey.key[31] &= 63 75 | privateKey.key[31] |= 64 76 | 77 | publicKey := ECPublicKey{} 78 | curve25519.ScalarBaseMult(&publicKey.key, &privateKey.key) 79 | 80 | return &ECKeyPair{ 81 | PrivateKey: privateKey, 82 | PublicKey: publicKey, 83 | } 84 | } 85 | 86 | // MakeECKeyPair creates a key pair. 87 | func MakeECKeyPair(privateKey, publicKey []byte) *ECKeyPair { 88 | return &ECKeyPair{ 89 | PrivateKey: *NewECPrivateKey(privateKey), 90 | PublicKey: *NewECPublicKey(publicKey), 91 | } 92 | } 93 | 94 | func (kp *ECKeyPair) String() string { 95 | return fmt.Sprintf("Public key : % 0X\nPrivate key: % 0X\n", kp.PublicKey.Key(), kp.PrivateKey.Key()) 96 | } 97 | 98 | // IdentityKey represents a Curve25519 public key used as a public identity. 99 | type IdentityKey struct { 100 | ECPublicKey 101 | } 102 | 103 | // NewIdentityKey initializes an identity key to a given value. 104 | func NewIdentityKey(b []byte) *IdentityKey { 105 | ensureKeyLength(b) 106 | k := &IdentityKey{} 107 | copy(k.key[:], b) 108 | return k 109 | } 110 | 111 | // IdentityKeyPair is a pair of private and public identity keys. 112 | type IdentityKeyPair struct { 113 | PrivateKey ECPrivateKey 114 | PublicKey IdentityKey 115 | } 116 | 117 | // NewIdentityKeyPairFromKeys initializes an identity key pair. 118 | func NewIdentityKeyPairFromKeys(priv, pub []byte) *IdentityKeyPair { 119 | return &IdentityKeyPair{ 120 | PublicKey: IdentityKey{*NewECPublicKey(pub)}, 121 | PrivateKey: *NewECPrivateKey(priv), 122 | } 123 | } 124 | 125 | // GenerateIdentityKeyPair is called once at install time to generate 126 | // the local identity keypair, which will be valid until a reinstallation. 127 | func GenerateIdentityKeyPair() *IdentityKeyPair { 128 | kp := NewECKeyPair() 129 | ikp := &IdentityKeyPair{ 130 | PublicKey: IdentityKey{kp.PublicKey}, 131 | PrivateKey: kp.PrivateKey, 132 | } 133 | return ikp 134 | } 135 | -------------------------------------------------------------------------------- /axolotl/keys_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import "testing" 7 | 8 | func TestKey(t *testing.T) { 9 | } 10 | -------------------------------------------------------------------------------- /axolotl/messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "crypto/hmac" 8 | "errors" 9 | 10 | protobuf "github.com/janimo/textsecure/axolotl/protobuf" 11 | 12 | "github.com/golang/protobuf/proto" 13 | ) 14 | 15 | // WhisperMessage represents the encrypted message format used in TextSecure. 16 | type WhisperMessage struct { 17 | Version byte 18 | RatchetKey *ECPublicKey 19 | Counter uint32 20 | PreviousCounter uint32 21 | Ciphertext []byte 22 | serialized []byte 23 | } 24 | 25 | const macLength = 8 26 | 27 | const currentVersion = 3 28 | 29 | func highBitsToInt(b byte) byte { 30 | return (b & 0xF0) >> 4 31 | } 32 | 33 | func makeVersionByte(hi, lo byte) byte { 34 | return (hi << 4) | lo 35 | } 36 | 37 | // ErrIncompleteWhisperMessage is returned when an incomplete WhisperMessage is received. 38 | var ErrIncompleteWhisperMessage = errors.New("incomplete WhisperMessage") 39 | 40 | // LoadWhisperMessage creates a WhisperMessage from serialized bytes. 41 | func LoadWhisperMessage(serialized []byte) (*WhisperMessage, error) { 42 | version := highBitsToInt(serialized[0]) 43 | message := serialized[1 : len(serialized)-macLength] 44 | 45 | if version != currentVersion { 46 | return nil, UnsupportedVersionError{version} 47 | } 48 | pwm := &protobuf.WhisperMessage{} 49 | err := proto.Unmarshal(message, pwm) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | if pwm.GetCiphertext() == nil || pwm.GetRatchetKey() == nil { 55 | return nil, ErrIncompleteWhisperMessage 56 | } 57 | 58 | wm := &WhisperMessage{ 59 | Version: version, 60 | Counter: pwm.GetCounter(), 61 | PreviousCounter: pwm.GetPreviousCounter(), 62 | RatchetKey: NewECPublicKey(pwm.GetRatchetKey()[1:]), 63 | Ciphertext: pwm.GetCiphertext(), 64 | serialized: serialized, 65 | } 66 | 67 | return wm, nil 68 | } 69 | 70 | func newWhisperMessage(messageVersion byte, macKey []byte, ratchetKey *ECPublicKey, 71 | counter, previousCounter uint32, ciphertext []byte, 72 | senderIdentity, receiverIdentity *IdentityKey) (*WhisperMessage, error) { 73 | 74 | version := makeVersionByte(messageVersion, currentVersion) 75 | 76 | pwm := &protobuf.WhisperMessage{ 77 | RatchetKey: ratchetKey.Serialize(), 78 | Counter: &counter, 79 | PreviousCounter: &previousCounter, 80 | Ciphertext: ciphertext, 81 | } 82 | 83 | message, err := proto.Marshal(pwm) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | data := append([]byte{version}, message...) 89 | mac := getMac(messageVersion, senderIdentity, receiverIdentity, macKey, data) 90 | 91 | wm := &WhisperMessage{ 92 | Version: messageVersion, 93 | Counter: counter, 94 | PreviousCounter: previousCounter, 95 | RatchetKey: ratchetKey, 96 | 97 | Ciphertext: ciphertext, 98 | serialized: append(data, mac...), 99 | } 100 | 101 | return wm, nil 102 | 103 | } 104 | 105 | func getMac(version byte, senderIdentity, receiverIdentity *IdentityKey, macKey, serialized []byte) []byte { 106 | msg := []byte{} 107 | if version >= 3 { 108 | msg = append(msg, senderIdentity.Serialize()...) 109 | msg = append(msg, receiverIdentity.Serialize()...) 110 | } 111 | msg = append(msg, serialized...) 112 | return ComputeTruncatedMAC(msg, macKey, macLength) 113 | } 114 | 115 | func (wm *WhisperMessage) verifyMAC(senderIdentity, receiverIdentity *IdentityKey, macKey []byte) bool { 116 | macpos := len(wm.serialized) - macLength 117 | 118 | ourMAC := getMac(wm.Version, senderIdentity, receiverIdentity, macKey, wm.serialized[:macpos]) 119 | theirMAC := wm.serialized[macpos:] 120 | return hmac.Equal(ourMAC, theirMAC) 121 | } 122 | 123 | func (wm *WhisperMessage) serialize() []byte { 124 | return wm.serialized 125 | } 126 | 127 | // PreKeyWhisperMessage represents a WhisperMessage and additional prekey 128 | // metadata used for the initial handshake in a conversation. 129 | type PreKeyWhisperMessage struct { 130 | Version byte 131 | RegistrationID uint32 132 | PreKeyID uint32 133 | SignedPreKeyID uint32 134 | BaseKey *ECPublicKey 135 | IdentityKey *IdentityKey 136 | Message *WhisperMessage 137 | serialized []byte 138 | } 139 | 140 | // ErrIncompletePreKeyWhisperMessage is returned when an incomplete PreKeyWhisperMessage is received. 141 | var ErrIncompletePreKeyWhisperMessage = errors.New("incomplete PreKeyWhisperMessage") 142 | 143 | // LoadPreKeyWhisperMessage creates a PreKeyWhisperMessage from serialized bytes. 144 | func LoadPreKeyWhisperMessage(serialized []byte) (*PreKeyWhisperMessage, error) { 145 | version := highBitsToInt(serialized[0]) 146 | 147 | if version != currentVersion { 148 | return nil, UnsupportedVersionError{version} 149 | } 150 | 151 | ppkwm := &protobuf.PreKeyWhisperMessage{} 152 | err := proto.Unmarshal(serialized[1:], ppkwm) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | if ppkwm.GetBaseKey() == nil || 158 | ppkwm.GetIdentityKey() == nil || 159 | ppkwm.GetMessage() == nil || 160 | ppkwm.GetSignedPreKeyId() == 0 { 161 | return nil, ErrIncompletePreKeyWhisperMessage 162 | } 163 | 164 | wm, err := LoadWhisperMessage(ppkwm.GetMessage()) 165 | if err != nil { 166 | return nil, err 167 | } 168 | pkwm := &PreKeyWhisperMessage{ 169 | Version: version, 170 | RegistrationID: ppkwm.GetRegistrationId(), 171 | PreKeyID: ppkwm.GetPreKeyId(), 172 | SignedPreKeyID: ppkwm.GetSignedPreKeyId(), 173 | BaseKey: NewECPublicKey(ppkwm.GetBaseKey()[1:]), 174 | IdentityKey: NewIdentityKey(ppkwm.GetIdentityKey()[1:]), 175 | Message: wm, 176 | serialized: serialized, 177 | } 178 | 179 | return pkwm, nil 180 | } 181 | 182 | func newPreKeyWhisperMessage(messageVersion byte, registrationID, preKeyID, signedPreKeyID uint32, baseKey *ECPublicKey, identityKey *IdentityKey, wm *WhisperMessage) (*PreKeyWhisperMessage, error) { 183 | 184 | ppkwm := &protobuf.PreKeyWhisperMessage{ 185 | RegistrationId: ®istrationID, 186 | PreKeyId: &preKeyID, 187 | SignedPreKeyId: &signedPreKeyID, 188 | BaseKey: baseKey.Serialize(), 189 | IdentityKey: identityKey.Serialize(), 190 | Message: wm.serialize(), 191 | } 192 | 193 | message, err := proto.Marshal(ppkwm) 194 | if err != nil { 195 | return nil, err 196 | } 197 | 198 | version := makeVersionByte(messageVersion, currentVersion) 199 | pkwm := &PreKeyWhisperMessage{ 200 | Version: version, 201 | RegistrationID: registrationID, 202 | PreKeyID: preKeyID, 203 | SignedPreKeyID: signedPreKeyID, 204 | BaseKey: baseKey, 205 | IdentityKey: identityKey, 206 | Message: wm, 207 | serialized: append([]byte{version}, message...), 208 | } 209 | return pkwm, nil 210 | } 211 | 212 | func (pkwm *PreKeyWhisperMessage) serialize() []byte { 213 | return pkwm.serialized 214 | } 215 | -------------------------------------------------------------------------------- /axolotl/messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMakeVersionByte(t *testing.T) { 13 | assert.Equal(t, byte(0x12), makeVersionByte(1, 2), "Make version byte") 14 | } 15 | -------------------------------------------------------------------------------- /axolotl/prekeys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | // PreKey and SignedPreKey support 7 | 8 | import ( 9 | "github.com/golang/protobuf/proto" 10 | protobuf "github.com/janimo/textsecure/axolotl/protobuf" 11 | ) 12 | 13 | var maxValue uint32 = 0xFFFFFF 14 | 15 | // PreKeyRecord represents a prekey, and is simply wrapper around the corresponding protobuf struct 16 | type PreKeyRecord struct { 17 | Pkrs *protobuf.PreKeyRecordStructure 18 | } 19 | 20 | // NewPreKeyRecord creates a new PreKeyRecord instance 21 | func NewPreKeyRecord(id uint32, kp *ECKeyPair) *PreKeyRecord { 22 | pkr := &PreKeyRecord{ 23 | &protobuf.PreKeyRecordStructure{ 24 | Id: &id, 25 | PublicKey: kp.PublicKey.Key()[:], 26 | PrivateKey: kp.PrivateKey.Key()[:], 27 | }, 28 | } 29 | return pkr 30 | } 31 | 32 | // LoadPreKeyRecord creates a PreKeyRecord instance from a serialized bytestream 33 | func LoadPreKeyRecord(serialized []byte) (*PreKeyRecord, error) { 34 | record := &PreKeyRecord{Pkrs: &protobuf.PreKeyRecordStructure{}} 35 | err := proto.Unmarshal(serialized, record.Pkrs) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return record, nil 40 | } 41 | 42 | // Serialize marshals the prekey into a protobuf. 43 | func (record *PreKeyRecord) Serialize() ([]byte, error) { 44 | b, err := proto.Marshal(record.Pkrs) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return b, nil 49 | } 50 | 51 | func (record *PreKeyRecord) getKeyPair() *ECKeyPair { 52 | return MakeECKeyPair(record.Pkrs.GetPrivateKey(), record.Pkrs.GetPublicKey()) 53 | } 54 | 55 | // GenerateLastResortPreKey creates the last resort PreKey. 56 | // Clients should do this only once, at install time, and durably store it for 57 | // the length of the install. 58 | func GenerateLastResortPreKey() *PreKeyRecord { 59 | return NewPreKeyRecord(maxValue, NewECKeyPair()) 60 | } 61 | 62 | // GeneratePreKeys creates a list of PreKeys. 63 | // Clients should do this at install time, and subsequently any time the list 64 | // of PreKeys stored on the server runs low. 65 | func GeneratePreKeys(start, count int) []*PreKeyRecord { 66 | records := make([]*PreKeyRecord, count) 67 | for i := 0; i < count; i++ { 68 | records[i] = NewPreKeyRecord(uint32(start+i), NewECKeyPair()) 69 | } 70 | return records 71 | } 72 | 73 | // SignedPreKeyRecord represents a prekey, and is simply wrapper around the corresponding protobuf struct 74 | type SignedPreKeyRecord struct { 75 | Spkrs *protobuf.SignedPreKeyRecordStructure 76 | } 77 | 78 | // NewSignedPreKeyRecord creates a new SignedPreKeyRecord instance 79 | func NewSignedPreKeyRecord(id uint32, timestamp uint64, kp *ECKeyPair, signature []byte) *SignedPreKeyRecord { 80 | return &SignedPreKeyRecord{ 81 | &protobuf.SignedPreKeyRecordStructure{ 82 | Id: &id, 83 | PublicKey: kp.PublicKey.Key()[:], 84 | PrivateKey: kp.PrivateKey.Key()[:], 85 | Timestamp: ×tamp, 86 | Signature: signature, 87 | }, 88 | } 89 | } 90 | 91 | // LoadSignedPreKeyRecord creates a SignedPreKeyRecord instance from a serialized bytestream 92 | func LoadSignedPreKeyRecord(serialized []byte) (*SignedPreKeyRecord, error) { 93 | record := &SignedPreKeyRecord{Spkrs: &protobuf.SignedPreKeyRecordStructure{}} 94 | err := proto.Unmarshal(serialized, record.Spkrs) 95 | if err != nil { 96 | return nil, err 97 | } 98 | return record, nil 99 | } 100 | 101 | // Serialize marshals the signed prekey into a protobuf. 102 | func (record *SignedPreKeyRecord) Serialize() ([]byte, error) { 103 | b, err := proto.Marshal(record.Spkrs) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return b, nil 108 | } 109 | 110 | func (record *SignedPreKeyRecord) getKeyPair() *ECKeyPair { 111 | return MakeECKeyPair(record.Spkrs.GetPrivateKey(), record.Spkrs.GetPublicKey()) 112 | } 113 | 114 | // PreKeyBundle contains the data required to initialize a sender session. 115 | // It is constructed from PreKeys registered by the peer. 116 | type PreKeyBundle struct { 117 | RegistrationID uint32 118 | DeviceID uint32 119 | 120 | PreKeyID uint32 121 | PreKeyPublic *ECPublicKey 122 | 123 | SignedPreKeyID int32 124 | SignedPreKeyPublic *ECPublicKey 125 | SignedPreKeySignature [64]byte 126 | 127 | IdentityKey *IdentityKey 128 | } 129 | 130 | // NewPreKeyBundle creates a PreKeyBundle structure with the given fields. 131 | func NewPreKeyBundle(registrationID, deviceID, preKeyID uint32, preKey *ECPublicKey, 132 | signedPreKeyID int32, signedPreKey *ECPublicKey, signature []byte, 133 | identityKey *IdentityKey) (*PreKeyBundle, error) { 134 | pkb := &PreKeyBundle{ 135 | RegistrationID: registrationID, 136 | DeviceID: deviceID, 137 | PreKeyID: preKeyID, 138 | PreKeyPublic: preKey, 139 | SignedPreKeyID: signedPreKeyID, 140 | SignedPreKeyPublic: signedPreKey, 141 | IdentityKey: identityKey, 142 | } 143 | copy(pkb.SignedPreKeySignature[:], signature) 144 | return pkb, nil 145 | } 146 | -------------------------------------------------------------------------------- /axolotl/prekeys_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | // An in-memory implementation of the prekey store, useful for testing 14 | type InMemoryPreKeyStore struct { 15 | s map[uint32][]byte 16 | } 17 | 18 | func NewInMemoryPreKeyStore() *InMemoryPreKeyStore { 19 | return &InMemoryPreKeyStore{ 20 | s: make(map[uint32][]byte), 21 | } 22 | } 23 | 24 | func (impks InMemoryPreKeyStore) containsPreKey(id uint32) bool { 25 | _, present := impks.s[id] 26 | return present 27 | } 28 | 29 | func (impks InMemoryPreKeyStore) removePreKey(id uint32) { 30 | delete(impks.s, id) 31 | } 32 | 33 | func (impks InMemoryPreKeyStore) loadPreKey(id uint32) (*PreKeyRecord, error) { 34 | if !impks.containsPreKey(id) { 35 | return nil, fmt.Errorf("key %d not found", id) 36 | } 37 | pkr, err := LoadPreKeyRecord(impks.s[id]) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return pkr, nil 42 | } 43 | 44 | func (impks InMemoryPreKeyStore) storePreKey(id uint32, pkr *PreKeyRecord) { 45 | impks.s[id], _ = pkr.Serialize() 46 | } 47 | 48 | func TestPreKeyStore(t *testing.T) { 49 | store := NewInMemoryPreKeyStore() 50 | regid := uint32(0x555) 51 | 52 | kp := NewECKeyPair() 53 | 54 | assert.False(t, store.containsPreKey(regid), "Store must be empty") 55 | 56 | pkr := NewPreKeyRecord(regid, kp) 57 | store.storePreKey(*pkr.Pkrs.Id, pkr) 58 | 59 | assert.True(t, store.containsPreKey(regid), "Store must contain regid") 60 | 61 | pkr, err := store.loadPreKey(regid) 62 | if assert.NoError(t, err) { 63 | assert.Equal(t, pkr.Pkrs.GetId(), regid, "The registration ids should be the same") 64 | assert.Equal(t, pkr.Pkrs.GetPublicKey(), kp.PublicKey.Key()[:], "The public keys should be the same") 65 | assert.Equal(t, pkr.Pkrs.GetPrivateKey(), kp.PrivateKey.Key()[:], "The private keys should be the same") 66 | } 67 | 68 | store.removePreKey(regid) 69 | 70 | assert.False(t, store.containsPreKey(regid), "Store must be empty") 71 | } 72 | -------------------------------------------------------------------------------- /axolotl/protobuf/LocalStorageProtocol.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: LocalStorageProtocol.proto 3 | // DO NOT EDIT! 4 | 5 | package axolotl 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type SessionStructure struct { 17 | SessionVersion *uint32 `protobuf:"varint,1,opt,name=sessionVersion" json:"sessionVersion,omitempty"` 18 | LocalIdentityPublic []byte `protobuf:"bytes,2,opt,name=localIdentityPublic" json:"localIdentityPublic,omitempty"` 19 | RemoteIdentityPublic []byte `protobuf:"bytes,3,opt,name=remoteIdentityPublic" json:"remoteIdentityPublic,omitempty"` 20 | RootKey []byte `protobuf:"bytes,4,opt,name=rootKey" json:"rootKey,omitempty"` 21 | PreviousCounter *uint32 `protobuf:"varint,5,opt,name=previousCounter" json:"previousCounter,omitempty"` 22 | SenderChain *SessionStructure_Chain `protobuf:"bytes,6,opt,name=senderChain" json:"senderChain,omitempty"` 23 | ReceiverChains []*SessionStructure_Chain `protobuf:"bytes,7,rep,name=receiverChains" json:"receiverChains,omitempty"` 24 | PendingKeyExchange *SessionStructure_PendingKeyExchange `protobuf:"bytes,8,opt,name=pendingKeyExchange" json:"pendingKeyExchange,omitempty"` 25 | PendingPreKey *SessionStructure_PendingPreKey `protobuf:"bytes,9,opt,name=pendingPreKey" json:"pendingPreKey,omitempty"` 26 | RemoteRegistrationId *uint32 `protobuf:"varint,10,opt,name=remoteRegistrationId" json:"remoteRegistrationId,omitempty"` 27 | LocalRegistrationId *uint32 `protobuf:"varint,11,opt,name=localRegistrationId" json:"localRegistrationId,omitempty"` 28 | NeedsRefresh *bool `protobuf:"varint,12,opt,name=needsRefresh" json:"needsRefresh,omitempty"` 29 | AliceBaseKey []byte `protobuf:"bytes,13,opt,name=aliceBaseKey" json:"aliceBaseKey,omitempty"` 30 | XXX_unrecognized []byte `json:"-"` 31 | } 32 | 33 | func (m *SessionStructure) Reset() { *m = SessionStructure{} } 34 | func (m *SessionStructure) String() string { return proto.CompactTextString(m) } 35 | func (*SessionStructure) ProtoMessage() {} 36 | 37 | func (m *SessionStructure) GetSessionVersion() uint32 { 38 | if m != nil && m.SessionVersion != nil { 39 | return *m.SessionVersion 40 | } 41 | return 0 42 | } 43 | 44 | func (m *SessionStructure) GetLocalIdentityPublic() []byte { 45 | if m != nil { 46 | return m.LocalIdentityPublic 47 | } 48 | return nil 49 | } 50 | 51 | func (m *SessionStructure) GetRemoteIdentityPublic() []byte { 52 | if m != nil { 53 | return m.RemoteIdentityPublic 54 | } 55 | return nil 56 | } 57 | 58 | func (m *SessionStructure) GetRootKey() []byte { 59 | if m != nil { 60 | return m.RootKey 61 | } 62 | return nil 63 | } 64 | 65 | func (m *SessionStructure) GetPreviousCounter() uint32 { 66 | if m != nil && m.PreviousCounter != nil { 67 | return *m.PreviousCounter 68 | } 69 | return 0 70 | } 71 | 72 | func (m *SessionStructure) GetSenderChain() *SessionStructure_Chain { 73 | if m != nil { 74 | return m.SenderChain 75 | } 76 | return nil 77 | } 78 | 79 | func (m *SessionStructure) GetReceiverChains() []*SessionStructure_Chain { 80 | if m != nil { 81 | return m.ReceiverChains 82 | } 83 | return nil 84 | } 85 | 86 | func (m *SessionStructure) GetPendingKeyExchange() *SessionStructure_PendingKeyExchange { 87 | if m != nil { 88 | return m.PendingKeyExchange 89 | } 90 | return nil 91 | } 92 | 93 | func (m *SessionStructure) GetPendingPreKey() *SessionStructure_PendingPreKey { 94 | if m != nil { 95 | return m.PendingPreKey 96 | } 97 | return nil 98 | } 99 | 100 | func (m *SessionStructure) GetRemoteRegistrationId() uint32 { 101 | if m != nil && m.RemoteRegistrationId != nil { 102 | return *m.RemoteRegistrationId 103 | } 104 | return 0 105 | } 106 | 107 | func (m *SessionStructure) GetLocalRegistrationId() uint32 { 108 | if m != nil && m.LocalRegistrationId != nil { 109 | return *m.LocalRegistrationId 110 | } 111 | return 0 112 | } 113 | 114 | func (m *SessionStructure) GetNeedsRefresh() bool { 115 | if m != nil && m.NeedsRefresh != nil { 116 | return *m.NeedsRefresh 117 | } 118 | return false 119 | } 120 | 121 | func (m *SessionStructure) GetAliceBaseKey() []byte { 122 | if m != nil { 123 | return m.AliceBaseKey 124 | } 125 | return nil 126 | } 127 | 128 | type SessionStructure_Chain struct { 129 | SenderRatchetKey []byte `protobuf:"bytes,1,opt,name=senderRatchetKey" json:"senderRatchetKey,omitempty"` 130 | SenderRatchetKeyPrivate []byte `protobuf:"bytes,2,opt,name=senderRatchetKeyPrivate" json:"senderRatchetKeyPrivate,omitempty"` 131 | ChainKey *SessionStructure_Chain_ChainKey `protobuf:"bytes,3,opt,name=chainKey" json:"chainKey,omitempty"` 132 | MessageKeys []*SessionStructure_Chain_MessageKey `protobuf:"bytes,4,rep,name=messageKeys" json:"messageKeys,omitempty"` 133 | XXX_unrecognized []byte `json:"-"` 134 | } 135 | 136 | func (m *SessionStructure_Chain) Reset() { *m = SessionStructure_Chain{} } 137 | func (m *SessionStructure_Chain) String() string { return proto.CompactTextString(m) } 138 | func (*SessionStructure_Chain) ProtoMessage() {} 139 | 140 | func (m *SessionStructure_Chain) GetSenderRatchetKey() []byte { 141 | if m != nil { 142 | return m.SenderRatchetKey 143 | } 144 | return nil 145 | } 146 | 147 | func (m *SessionStructure_Chain) GetSenderRatchetKeyPrivate() []byte { 148 | if m != nil { 149 | return m.SenderRatchetKeyPrivate 150 | } 151 | return nil 152 | } 153 | 154 | func (m *SessionStructure_Chain) GetChainKey() *SessionStructure_Chain_ChainKey { 155 | if m != nil { 156 | return m.ChainKey 157 | } 158 | return nil 159 | } 160 | 161 | func (m *SessionStructure_Chain) GetMessageKeys() []*SessionStructure_Chain_MessageKey { 162 | if m != nil { 163 | return m.MessageKeys 164 | } 165 | return nil 166 | } 167 | 168 | type SessionStructure_Chain_ChainKey struct { 169 | Index *uint32 `protobuf:"varint,1,opt,name=index" json:"index,omitempty"` 170 | Key []byte `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` 171 | XXX_unrecognized []byte `json:"-"` 172 | } 173 | 174 | func (m *SessionStructure_Chain_ChainKey) Reset() { *m = SessionStructure_Chain_ChainKey{} } 175 | func (m *SessionStructure_Chain_ChainKey) String() string { return proto.CompactTextString(m) } 176 | func (*SessionStructure_Chain_ChainKey) ProtoMessage() {} 177 | 178 | func (m *SessionStructure_Chain_ChainKey) GetIndex() uint32 { 179 | if m != nil && m.Index != nil { 180 | return *m.Index 181 | } 182 | return 0 183 | } 184 | 185 | func (m *SessionStructure_Chain_ChainKey) GetKey() []byte { 186 | if m != nil { 187 | return m.Key 188 | } 189 | return nil 190 | } 191 | 192 | type SessionStructure_Chain_MessageKey struct { 193 | Index *uint32 `protobuf:"varint,1,opt,name=index" json:"index,omitempty"` 194 | CipherKey []byte `protobuf:"bytes,2,opt,name=cipherKey" json:"cipherKey,omitempty"` 195 | MacKey []byte `protobuf:"bytes,3,opt,name=macKey" json:"macKey,omitempty"` 196 | Iv []byte `protobuf:"bytes,4,opt,name=iv" json:"iv,omitempty"` 197 | XXX_unrecognized []byte `json:"-"` 198 | } 199 | 200 | func (m *SessionStructure_Chain_MessageKey) Reset() { *m = SessionStructure_Chain_MessageKey{} } 201 | func (m *SessionStructure_Chain_MessageKey) String() string { return proto.CompactTextString(m) } 202 | func (*SessionStructure_Chain_MessageKey) ProtoMessage() {} 203 | 204 | func (m *SessionStructure_Chain_MessageKey) GetIndex() uint32 { 205 | if m != nil && m.Index != nil { 206 | return *m.Index 207 | } 208 | return 0 209 | } 210 | 211 | func (m *SessionStructure_Chain_MessageKey) GetCipherKey() []byte { 212 | if m != nil { 213 | return m.CipherKey 214 | } 215 | return nil 216 | } 217 | 218 | func (m *SessionStructure_Chain_MessageKey) GetMacKey() []byte { 219 | if m != nil { 220 | return m.MacKey 221 | } 222 | return nil 223 | } 224 | 225 | func (m *SessionStructure_Chain_MessageKey) GetIv() []byte { 226 | if m != nil { 227 | return m.Iv 228 | } 229 | return nil 230 | } 231 | 232 | type SessionStructure_PendingKeyExchange struct { 233 | Sequence *uint32 `protobuf:"varint,1,opt,name=sequence" json:"sequence,omitempty"` 234 | LocalBaseKey []byte `protobuf:"bytes,2,opt,name=localBaseKey" json:"localBaseKey,omitempty"` 235 | LocalBaseKeyPrivate []byte `protobuf:"bytes,3,opt,name=localBaseKeyPrivate" json:"localBaseKeyPrivate,omitempty"` 236 | LocalRatchetKey []byte `protobuf:"bytes,4,opt,name=localRatchetKey" json:"localRatchetKey,omitempty"` 237 | LocalRatchetKeyPrivate []byte `protobuf:"bytes,5,opt,name=localRatchetKeyPrivate" json:"localRatchetKeyPrivate,omitempty"` 238 | LocalIdentityKey []byte `protobuf:"bytes,7,opt,name=localIdentityKey" json:"localIdentityKey,omitempty"` 239 | LocalIdentityKeyPrivate []byte `protobuf:"bytes,8,opt,name=localIdentityKeyPrivate" json:"localIdentityKeyPrivate,omitempty"` 240 | XXX_unrecognized []byte `json:"-"` 241 | } 242 | 243 | func (m *SessionStructure_PendingKeyExchange) Reset() { *m = SessionStructure_PendingKeyExchange{} } 244 | func (m *SessionStructure_PendingKeyExchange) String() string { return proto.CompactTextString(m) } 245 | func (*SessionStructure_PendingKeyExchange) ProtoMessage() {} 246 | 247 | func (m *SessionStructure_PendingKeyExchange) GetSequence() uint32 { 248 | if m != nil && m.Sequence != nil { 249 | return *m.Sequence 250 | } 251 | return 0 252 | } 253 | 254 | func (m *SessionStructure_PendingKeyExchange) GetLocalBaseKey() []byte { 255 | if m != nil { 256 | return m.LocalBaseKey 257 | } 258 | return nil 259 | } 260 | 261 | func (m *SessionStructure_PendingKeyExchange) GetLocalBaseKeyPrivate() []byte { 262 | if m != nil { 263 | return m.LocalBaseKeyPrivate 264 | } 265 | return nil 266 | } 267 | 268 | func (m *SessionStructure_PendingKeyExchange) GetLocalRatchetKey() []byte { 269 | if m != nil { 270 | return m.LocalRatchetKey 271 | } 272 | return nil 273 | } 274 | 275 | func (m *SessionStructure_PendingKeyExchange) GetLocalRatchetKeyPrivate() []byte { 276 | if m != nil { 277 | return m.LocalRatchetKeyPrivate 278 | } 279 | return nil 280 | } 281 | 282 | func (m *SessionStructure_PendingKeyExchange) GetLocalIdentityKey() []byte { 283 | if m != nil { 284 | return m.LocalIdentityKey 285 | } 286 | return nil 287 | } 288 | 289 | func (m *SessionStructure_PendingKeyExchange) GetLocalIdentityKeyPrivate() []byte { 290 | if m != nil { 291 | return m.LocalIdentityKeyPrivate 292 | } 293 | return nil 294 | } 295 | 296 | type SessionStructure_PendingPreKey struct { 297 | PreKeyId *uint32 `protobuf:"varint,1,opt,name=preKeyId" json:"preKeyId,omitempty"` 298 | SignedPreKeyId *int32 `protobuf:"varint,3,opt,name=signedPreKeyId" json:"signedPreKeyId,omitempty"` 299 | BaseKey []byte `protobuf:"bytes,2,opt,name=baseKey" json:"baseKey,omitempty"` 300 | XXX_unrecognized []byte `json:"-"` 301 | } 302 | 303 | func (m *SessionStructure_PendingPreKey) Reset() { *m = SessionStructure_PendingPreKey{} } 304 | func (m *SessionStructure_PendingPreKey) String() string { return proto.CompactTextString(m) } 305 | func (*SessionStructure_PendingPreKey) ProtoMessage() {} 306 | 307 | func (m *SessionStructure_PendingPreKey) GetPreKeyId() uint32 { 308 | if m != nil && m.PreKeyId != nil { 309 | return *m.PreKeyId 310 | } 311 | return 0 312 | } 313 | 314 | func (m *SessionStructure_PendingPreKey) GetSignedPreKeyId() int32 { 315 | if m != nil && m.SignedPreKeyId != nil { 316 | return *m.SignedPreKeyId 317 | } 318 | return 0 319 | } 320 | 321 | func (m *SessionStructure_PendingPreKey) GetBaseKey() []byte { 322 | if m != nil { 323 | return m.BaseKey 324 | } 325 | return nil 326 | } 327 | 328 | type RecordStructure struct { 329 | CurrentSession *SessionStructure `protobuf:"bytes,1,opt,name=currentSession" json:"currentSession,omitempty"` 330 | PreviousSessions []*SessionStructure `protobuf:"bytes,2,rep,name=previousSessions" json:"previousSessions,omitempty"` 331 | XXX_unrecognized []byte `json:"-"` 332 | } 333 | 334 | func (m *RecordStructure) Reset() { *m = RecordStructure{} } 335 | func (m *RecordStructure) String() string { return proto.CompactTextString(m) } 336 | func (*RecordStructure) ProtoMessage() {} 337 | 338 | func (m *RecordStructure) GetCurrentSession() *SessionStructure { 339 | if m != nil { 340 | return m.CurrentSession 341 | } 342 | return nil 343 | } 344 | 345 | func (m *RecordStructure) GetPreviousSessions() []*SessionStructure { 346 | if m != nil { 347 | return m.PreviousSessions 348 | } 349 | return nil 350 | } 351 | 352 | type PreKeyRecordStructure struct { 353 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 354 | PublicKey []byte `protobuf:"bytes,2,opt,name=publicKey" json:"publicKey,omitempty"` 355 | PrivateKey []byte `protobuf:"bytes,3,opt,name=privateKey" json:"privateKey,omitempty"` 356 | XXX_unrecognized []byte `json:"-"` 357 | } 358 | 359 | func (m *PreKeyRecordStructure) Reset() { *m = PreKeyRecordStructure{} } 360 | func (m *PreKeyRecordStructure) String() string { return proto.CompactTextString(m) } 361 | func (*PreKeyRecordStructure) ProtoMessage() {} 362 | 363 | func (m *PreKeyRecordStructure) GetId() uint32 { 364 | if m != nil && m.Id != nil { 365 | return *m.Id 366 | } 367 | return 0 368 | } 369 | 370 | func (m *PreKeyRecordStructure) GetPublicKey() []byte { 371 | if m != nil { 372 | return m.PublicKey 373 | } 374 | return nil 375 | } 376 | 377 | func (m *PreKeyRecordStructure) GetPrivateKey() []byte { 378 | if m != nil { 379 | return m.PrivateKey 380 | } 381 | return nil 382 | } 383 | 384 | type SignedPreKeyRecordStructure struct { 385 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 386 | PublicKey []byte `protobuf:"bytes,2,opt,name=publicKey" json:"publicKey,omitempty"` 387 | PrivateKey []byte `protobuf:"bytes,3,opt,name=privateKey" json:"privateKey,omitempty"` 388 | Signature []byte `protobuf:"bytes,4,opt,name=signature" json:"signature,omitempty"` 389 | Timestamp *uint64 `protobuf:"fixed64,5,opt,name=timestamp" json:"timestamp,omitempty"` 390 | XXX_unrecognized []byte `json:"-"` 391 | } 392 | 393 | func (m *SignedPreKeyRecordStructure) Reset() { *m = SignedPreKeyRecordStructure{} } 394 | func (m *SignedPreKeyRecordStructure) String() string { return proto.CompactTextString(m) } 395 | func (*SignedPreKeyRecordStructure) ProtoMessage() {} 396 | 397 | func (m *SignedPreKeyRecordStructure) GetId() uint32 { 398 | if m != nil && m.Id != nil { 399 | return *m.Id 400 | } 401 | return 0 402 | } 403 | 404 | func (m *SignedPreKeyRecordStructure) GetPublicKey() []byte { 405 | if m != nil { 406 | return m.PublicKey 407 | } 408 | return nil 409 | } 410 | 411 | func (m *SignedPreKeyRecordStructure) GetPrivateKey() []byte { 412 | if m != nil { 413 | return m.PrivateKey 414 | } 415 | return nil 416 | } 417 | 418 | func (m *SignedPreKeyRecordStructure) GetSignature() []byte { 419 | if m != nil { 420 | return m.Signature 421 | } 422 | return nil 423 | } 424 | 425 | func (m *SignedPreKeyRecordStructure) GetTimestamp() uint64 { 426 | if m != nil && m.Timestamp != nil { 427 | return *m.Timestamp 428 | } 429 | return 0 430 | } 431 | 432 | type IdentityKeyPairStructure struct { 433 | PublicKey []byte `protobuf:"bytes,1,opt,name=publicKey" json:"publicKey,omitempty"` 434 | PrivateKey []byte `protobuf:"bytes,2,opt,name=privateKey" json:"privateKey,omitempty"` 435 | XXX_unrecognized []byte `json:"-"` 436 | } 437 | 438 | func (m *IdentityKeyPairStructure) Reset() { *m = IdentityKeyPairStructure{} } 439 | func (m *IdentityKeyPairStructure) String() string { return proto.CompactTextString(m) } 440 | func (*IdentityKeyPairStructure) ProtoMessage() {} 441 | 442 | func (m *IdentityKeyPairStructure) GetPublicKey() []byte { 443 | if m != nil { 444 | return m.PublicKey 445 | } 446 | return nil 447 | } 448 | 449 | func (m *IdentityKeyPairStructure) GetPrivateKey() []byte { 450 | if m != nil { 451 | return m.PrivateKey 452 | } 453 | return nil 454 | } 455 | 456 | type SenderKeyStateStructure struct { 457 | SenderKeyId *uint32 `protobuf:"varint,1,opt,name=senderKeyId" json:"senderKeyId,omitempty"` 458 | SenderChainKey *SenderKeyStateStructure_SenderChainKey `protobuf:"bytes,2,opt,name=senderChainKey" json:"senderChainKey,omitempty"` 459 | SenderSigningKey *SenderKeyStateStructure_SenderSigningKey `protobuf:"bytes,3,opt,name=senderSigningKey" json:"senderSigningKey,omitempty"` 460 | SenderMessageKeys []*SenderKeyStateStructure_SenderMessageKey `protobuf:"bytes,4,rep,name=senderMessageKeys" json:"senderMessageKeys,omitempty"` 461 | XXX_unrecognized []byte `json:"-"` 462 | } 463 | 464 | func (m *SenderKeyStateStructure) Reset() { *m = SenderKeyStateStructure{} } 465 | func (m *SenderKeyStateStructure) String() string { return proto.CompactTextString(m) } 466 | func (*SenderKeyStateStructure) ProtoMessage() {} 467 | 468 | func (m *SenderKeyStateStructure) GetSenderKeyId() uint32 { 469 | if m != nil && m.SenderKeyId != nil { 470 | return *m.SenderKeyId 471 | } 472 | return 0 473 | } 474 | 475 | func (m *SenderKeyStateStructure) GetSenderChainKey() *SenderKeyStateStructure_SenderChainKey { 476 | if m != nil { 477 | return m.SenderChainKey 478 | } 479 | return nil 480 | } 481 | 482 | func (m *SenderKeyStateStructure) GetSenderSigningKey() *SenderKeyStateStructure_SenderSigningKey { 483 | if m != nil { 484 | return m.SenderSigningKey 485 | } 486 | return nil 487 | } 488 | 489 | func (m *SenderKeyStateStructure) GetSenderMessageKeys() []*SenderKeyStateStructure_SenderMessageKey { 490 | if m != nil { 491 | return m.SenderMessageKeys 492 | } 493 | return nil 494 | } 495 | 496 | type SenderKeyStateStructure_SenderChainKey struct { 497 | Iteration *uint32 `protobuf:"varint,1,opt,name=iteration" json:"iteration,omitempty"` 498 | Seed []byte `protobuf:"bytes,2,opt,name=seed" json:"seed,omitempty"` 499 | XXX_unrecognized []byte `json:"-"` 500 | } 501 | 502 | func (m *SenderKeyStateStructure_SenderChainKey) Reset() { 503 | *m = SenderKeyStateStructure_SenderChainKey{} 504 | } 505 | func (m *SenderKeyStateStructure_SenderChainKey) String() string { return proto.CompactTextString(m) } 506 | func (*SenderKeyStateStructure_SenderChainKey) ProtoMessage() {} 507 | 508 | func (m *SenderKeyStateStructure_SenderChainKey) GetIteration() uint32 { 509 | if m != nil && m.Iteration != nil { 510 | return *m.Iteration 511 | } 512 | return 0 513 | } 514 | 515 | func (m *SenderKeyStateStructure_SenderChainKey) GetSeed() []byte { 516 | if m != nil { 517 | return m.Seed 518 | } 519 | return nil 520 | } 521 | 522 | type SenderKeyStateStructure_SenderMessageKey struct { 523 | Iteration *uint32 `protobuf:"varint,1,opt,name=iteration" json:"iteration,omitempty"` 524 | Seed []byte `protobuf:"bytes,2,opt,name=seed" json:"seed,omitempty"` 525 | XXX_unrecognized []byte `json:"-"` 526 | } 527 | 528 | func (m *SenderKeyStateStructure_SenderMessageKey) Reset() { 529 | *m = SenderKeyStateStructure_SenderMessageKey{} 530 | } 531 | func (m *SenderKeyStateStructure_SenderMessageKey) String() string { return proto.CompactTextString(m) } 532 | func (*SenderKeyStateStructure_SenderMessageKey) ProtoMessage() {} 533 | 534 | func (m *SenderKeyStateStructure_SenderMessageKey) GetIteration() uint32 { 535 | if m != nil && m.Iteration != nil { 536 | return *m.Iteration 537 | } 538 | return 0 539 | } 540 | 541 | func (m *SenderKeyStateStructure_SenderMessageKey) GetSeed() []byte { 542 | if m != nil { 543 | return m.Seed 544 | } 545 | return nil 546 | } 547 | 548 | type SenderKeyStateStructure_SenderSigningKey struct { 549 | Public []byte `protobuf:"bytes,1,opt,name=public" json:"public,omitempty"` 550 | Private []byte `protobuf:"bytes,2,opt,name=private" json:"private,omitempty"` 551 | XXX_unrecognized []byte `json:"-"` 552 | } 553 | 554 | func (m *SenderKeyStateStructure_SenderSigningKey) Reset() { 555 | *m = SenderKeyStateStructure_SenderSigningKey{} 556 | } 557 | func (m *SenderKeyStateStructure_SenderSigningKey) String() string { return proto.CompactTextString(m) } 558 | func (*SenderKeyStateStructure_SenderSigningKey) ProtoMessage() {} 559 | 560 | func (m *SenderKeyStateStructure_SenderSigningKey) GetPublic() []byte { 561 | if m != nil { 562 | return m.Public 563 | } 564 | return nil 565 | } 566 | 567 | func (m *SenderKeyStateStructure_SenderSigningKey) GetPrivate() []byte { 568 | if m != nil { 569 | return m.Private 570 | } 571 | return nil 572 | } 573 | 574 | type SenderKeyRecordStructure struct { 575 | SenderKeyStates []*SenderKeyStateStructure `protobuf:"bytes,1,rep,name=senderKeyStates" json:"senderKeyStates,omitempty"` 576 | XXX_unrecognized []byte `json:"-"` 577 | } 578 | 579 | func (m *SenderKeyRecordStructure) Reset() { *m = SenderKeyRecordStructure{} } 580 | func (m *SenderKeyRecordStructure) String() string { return proto.CompactTextString(m) } 581 | func (*SenderKeyRecordStructure) ProtoMessage() {} 582 | 583 | func (m *SenderKeyRecordStructure) GetSenderKeyStates() []*SenderKeyStateStructure { 584 | if m != nil { 585 | return m.SenderKeyStates 586 | } 587 | return nil 588 | } 589 | 590 | func init() { 591 | } 592 | -------------------------------------------------------------------------------- /axolotl/protobuf/LocalStorageProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libaxolotl.state"; 4 | option java_outer_classname = "StorageProtos"; 5 | 6 | message SessionStructure { 7 | message Chain { 8 | optional bytes senderRatchetKey = 1; 9 | optional bytes senderRatchetKeyPrivate = 2; 10 | 11 | message ChainKey { 12 | optional uint32 index = 1; 13 | optional bytes key = 2; 14 | } 15 | 16 | optional ChainKey chainKey = 3; 17 | 18 | message MessageKey { 19 | optional uint32 index = 1; 20 | optional bytes cipherKey = 2; 21 | optional bytes macKey = 3; 22 | optional bytes iv = 4; 23 | } 24 | 25 | repeated MessageKey messageKeys = 4; 26 | } 27 | 28 | message PendingKeyExchange { 29 | optional uint32 sequence = 1; 30 | optional bytes localBaseKey = 2; 31 | optional bytes localBaseKeyPrivate = 3; 32 | optional bytes localRatchetKey = 4; 33 | optional bytes localRatchetKeyPrivate = 5; 34 | optional bytes localIdentityKey = 7; 35 | optional bytes localIdentityKeyPrivate = 8; 36 | } 37 | 38 | message PendingPreKey { 39 | optional uint32 preKeyId = 1; 40 | optional int32 signedPreKeyId = 3; 41 | optional bytes baseKey = 2; 42 | } 43 | 44 | optional uint32 sessionVersion = 1; 45 | optional bytes localIdentityPublic = 2; 46 | optional bytes remoteIdentityPublic = 3; 47 | 48 | optional bytes rootKey = 4; 49 | optional uint32 previousCounter = 5; 50 | 51 | optional Chain senderChain = 6; 52 | repeated Chain receiverChains = 7; 53 | 54 | optional PendingKeyExchange pendingKeyExchange = 8; 55 | optional PendingPreKey pendingPreKey = 9; 56 | 57 | optional uint32 remoteRegistrationId = 10; 58 | optional uint32 localRegistrationId = 11; 59 | 60 | optional bool needsRefresh = 12; 61 | optional bytes aliceBaseKey = 13; 62 | } 63 | 64 | message RecordStructure { 65 | optional SessionStructure currentSession = 1; 66 | repeated SessionStructure previousSessions = 2; 67 | } 68 | 69 | message PreKeyRecordStructure { 70 | optional uint32 id = 1; 71 | optional bytes publicKey = 2; 72 | optional bytes privateKey = 3; 73 | } 74 | 75 | message SignedPreKeyRecordStructure { 76 | optional uint32 id = 1; 77 | optional bytes publicKey = 2; 78 | optional bytes privateKey = 3; 79 | optional bytes signature = 4; 80 | optional fixed64 timestamp = 5; 81 | } 82 | 83 | message IdentityKeyPairStructure { 84 | optional bytes publicKey = 1; 85 | optional bytes privateKey = 2; 86 | } 87 | 88 | message SenderKeyStateStructure { 89 | message SenderChainKey { 90 | optional uint32 iteration = 1; 91 | optional bytes seed = 2; 92 | } 93 | 94 | message SenderMessageKey { 95 | optional uint32 iteration = 1; 96 | optional bytes seed = 2; 97 | } 98 | 99 | message SenderSigningKey { 100 | optional bytes public = 1; 101 | optional bytes private = 2; 102 | } 103 | 104 | optional uint32 senderKeyId = 1; 105 | optional SenderChainKey senderChainKey = 2; 106 | optional SenderSigningKey senderSigningKey = 3; 107 | repeated SenderMessageKey senderMessageKeys = 4; 108 | } 109 | 110 | message SenderKeyRecordStructure { 111 | repeated SenderKeyStateStructure senderKeyStates = 1; 112 | } 113 | -------------------------------------------------------------------------------- /axolotl/protobuf/WhisperTextProtocol.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: WhisperTextProtocol.proto 3 | // DO NOT EDIT! 4 | 5 | package axolotl 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type WhisperMessage struct { 17 | RatchetKey []byte `protobuf:"bytes,1,opt,name=ratchetKey" json:"ratchetKey,omitempty"` 18 | Counter *uint32 `protobuf:"varint,2,opt,name=counter" json:"counter,omitempty"` 19 | PreviousCounter *uint32 `protobuf:"varint,3,opt,name=previousCounter" json:"previousCounter,omitempty"` 20 | Ciphertext []byte `protobuf:"bytes,4,opt,name=ciphertext" json:"ciphertext,omitempty"` 21 | XXX_unrecognized []byte `json:"-"` 22 | } 23 | 24 | func (m *WhisperMessage) Reset() { *m = WhisperMessage{} } 25 | func (m *WhisperMessage) String() string { return proto.CompactTextString(m) } 26 | func (*WhisperMessage) ProtoMessage() {} 27 | 28 | func (m *WhisperMessage) GetRatchetKey() []byte { 29 | if m != nil { 30 | return m.RatchetKey 31 | } 32 | return nil 33 | } 34 | 35 | func (m *WhisperMessage) GetCounter() uint32 { 36 | if m != nil && m.Counter != nil { 37 | return *m.Counter 38 | } 39 | return 0 40 | } 41 | 42 | func (m *WhisperMessage) GetPreviousCounter() uint32 { 43 | if m != nil && m.PreviousCounter != nil { 44 | return *m.PreviousCounter 45 | } 46 | return 0 47 | } 48 | 49 | func (m *WhisperMessage) GetCiphertext() []byte { 50 | if m != nil { 51 | return m.Ciphertext 52 | } 53 | return nil 54 | } 55 | 56 | type PreKeyWhisperMessage struct { 57 | RegistrationId *uint32 `protobuf:"varint,5,opt,name=registrationId" json:"registrationId,omitempty"` 58 | PreKeyId *uint32 `protobuf:"varint,1,opt,name=preKeyId" json:"preKeyId,omitempty"` 59 | SignedPreKeyId *uint32 `protobuf:"varint,6,opt,name=signedPreKeyId" json:"signedPreKeyId,omitempty"` 60 | BaseKey []byte `protobuf:"bytes,2,opt,name=baseKey" json:"baseKey,omitempty"` 61 | IdentityKey []byte `protobuf:"bytes,3,opt,name=identityKey" json:"identityKey,omitempty"` 62 | Message []byte `protobuf:"bytes,4,opt,name=message" json:"message,omitempty"` 63 | XXX_unrecognized []byte `json:"-"` 64 | } 65 | 66 | func (m *PreKeyWhisperMessage) Reset() { *m = PreKeyWhisperMessage{} } 67 | func (m *PreKeyWhisperMessage) String() string { return proto.CompactTextString(m) } 68 | func (*PreKeyWhisperMessage) ProtoMessage() {} 69 | 70 | func (m *PreKeyWhisperMessage) GetRegistrationId() uint32 { 71 | if m != nil && m.RegistrationId != nil { 72 | return *m.RegistrationId 73 | } 74 | return 0 75 | } 76 | 77 | func (m *PreKeyWhisperMessage) GetPreKeyId() uint32 { 78 | if m != nil && m.PreKeyId != nil { 79 | return *m.PreKeyId 80 | } 81 | return 0 82 | } 83 | 84 | func (m *PreKeyWhisperMessage) GetSignedPreKeyId() uint32 { 85 | if m != nil && m.SignedPreKeyId != nil { 86 | return *m.SignedPreKeyId 87 | } 88 | return 0 89 | } 90 | 91 | func (m *PreKeyWhisperMessage) GetBaseKey() []byte { 92 | if m != nil { 93 | return m.BaseKey 94 | } 95 | return nil 96 | } 97 | 98 | func (m *PreKeyWhisperMessage) GetIdentityKey() []byte { 99 | if m != nil { 100 | return m.IdentityKey 101 | } 102 | return nil 103 | } 104 | 105 | func (m *PreKeyWhisperMessage) GetMessage() []byte { 106 | if m != nil { 107 | return m.Message 108 | } 109 | return nil 110 | } 111 | 112 | type KeyExchangeMessage struct { 113 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 114 | BaseKey []byte `protobuf:"bytes,2,opt,name=baseKey" json:"baseKey,omitempty"` 115 | RatchetKey []byte `protobuf:"bytes,3,opt,name=ratchetKey" json:"ratchetKey,omitempty"` 116 | IdentityKey []byte `protobuf:"bytes,4,opt,name=identityKey" json:"identityKey,omitempty"` 117 | BaseKeySignature []byte `protobuf:"bytes,5,opt,name=baseKeySignature" json:"baseKeySignature,omitempty"` 118 | XXX_unrecognized []byte `json:"-"` 119 | } 120 | 121 | func (m *KeyExchangeMessage) Reset() { *m = KeyExchangeMessage{} } 122 | func (m *KeyExchangeMessage) String() string { return proto.CompactTextString(m) } 123 | func (*KeyExchangeMessage) ProtoMessage() {} 124 | 125 | func (m *KeyExchangeMessage) GetId() uint32 { 126 | if m != nil && m.Id != nil { 127 | return *m.Id 128 | } 129 | return 0 130 | } 131 | 132 | func (m *KeyExchangeMessage) GetBaseKey() []byte { 133 | if m != nil { 134 | return m.BaseKey 135 | } 136 | return nil 137 | } 138 | 139 | func (m *KeyExchangeMessage) GetRatchetKey() []byte { 140 | if m != nil { 141 | return m.RatchetKey 142 | } 143 | return nil 144 | } 145 | 146 | func (m *KeyExchangeMessage) GetIdentityKey() []byte { 147 | if m != nil { 148 | return m.IdentityKey 149 | } 150 | return nil 151 | } 152 | 153 | func (m *KeyExchangeMessage) GetBaseKeySignature() []byte { 154 | if m != nil { 155 | return m.BaseKeySignature 156 | } 157 | return nil 158 | } 159 | 160 | type SenderKeyMessage struct { 161 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 162 | Iteration *uint32 `protobuf:"varint,2,opt,name=iteration" json:"iteration,omitempty"` 163 | Ciphertext []byte `protobuf:"bytes,3,opt,name=ciphertext" json:"ciphertext,omitempty"` 164 | XXX_unrecognized []byte `json:"-"` 165 | } 166 | 167 | func (m *SenderKeyMessage) Reset() { *m = SenderKeyMessage{} } 168 | func (m *SenderKeyMessage) String() string { return proto.CompactTextString(m) } 169 | func (*SenderKeyMessage) ProtoMessage() {} 170 | 171 | func (m *SenderKeyMessage) GetId() uint32 { 172 | if m != nil && m.Id != nil { 173 | return *m.Id 174 | } 175 | return 0 176 | } 177 | 178 | func (m *SenderKeyMessage) GetIteration() uint32 { 179 | if m != nil && m.Iteration != nil { 180 | return *m.Iteration 181 | } 182 | return 0 183 | } 184 | 185 | func (m *SenderKeyMessage) GetCiphertext() []byte { 186 | if m != nil { 187 | return m.Ciphertext 188 | } 189 | return nil 190 | } 191 | 192 | type SenderKeyDistributionMessage struct { 193 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 194 | Iteration *uint32 `protobuf:"varint,2,opt,name=iteration" json:"iteration,omitempty"` 195 | ChainKey []byte `protobuf:"bytes,3,opt,name=chainKey" json:"chainKey,omitempty"` 196 | SigningKey []byte `protobuf:"bytes,4,opt,name=signingKey" json:"signingKey,omitempty"` 197 | XXX_unrecognized []byte `json:"-"` 198 | } 199 | 200 | func (m *SenderKeyDistributionMessage) Reset() { *m = SenderKeyDistributionMessage{} } 201 | func (m *SenderKeyDistributionMessage) String() string { return proto.CompactTextString(m) } 202 | func (*SenderKeyDistributionMessage) ProtoMessage() {} 203 | 204 | func (m *SenderKeyDistributionMessage) GetId() uint32 { 205 | if m != nil && m.Id != nil { 206 | return *m.Id 207 | } 208 | return 0 209 | } 210 | 211 | func (m *SenderKeyDistributionMessage) GetIteration() uint32 { 212 | if m != nil && m.Iteration != nil { 213 | return *m.Iteration 214 | } 215 | return 0 216 | } 217 | 218 | func (m *SenderKeyDistributionMessage) GetChainKey() []byte { 219 | if m != nil { 220 | return m.ChainKey 221 | } 222 | return nil 223 | } 224 | 225 | func (m *SenderKeyDistributionMessage) GetSigningKey() []byte { 226 | if m != nil { 227 | return m.SigningKey 228 | } 229 | return nil 230 | } 231 | 232 | func init() { 233 | } 234 | -------------------------------------------------------------------------------- /axolotl/protobuf/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | package axolotl; 2 | 3 | option java_package = "org.whispersystems.libaxolotl.protocol"; 4 | option java_outer_classname = "WhisperProtos"; 5 | 6 | message WhisperMessage { 7 | optional bytes ratchetKey = 1; 8 | optional uint32 counter = 2; 9 | optional uint32 previousCounter = 3; 10 | optional bytes ciphertext = 4; 11 | } 12 | 13 | message PreKeyWhisperMessage { 14 | optional uint32 registrationId = 5; 15 | optional uint32 preKeyId = 1; 16 | optional uint32 signedPreKeyId = 6; 17 | optional bytes baseKey = 2; 18 | optional bytes identityKey = 3; 19 | optional bytes message = 4; // WhisperMessage 20 | } 21 | 22 | message KeyExchangeMessage { 23 | optional uint32 id = 1; 24 | optional bytes baseKey = 2; 25 | optional bytes ratchetKey = 3; 26 | optional bytes identityKey = 4; 27 | optional bytes baseKeySignature = 5; 28 | } 29 | 30 | message SenderKeyMessage { 31 | optional uint32 id = 1; 32 | optional uint32 iteration = 2; 33 | optional bytes ciphertext = 3; 34 | } 35 | 36 | message SenderKeyDistributionMessage { 37 | optional uint32 id = 1; 38 | optional uint32 iteration = 2; 39 | optional bytes chainKey = 3; 40 | optional bytes signingKey = 4; 41 | } 42 | -------------------------------------------------------------------------------- /axolotl/ratchet.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | import ( 7 | "crypto/hmac" 8 | "crypto/sha256" 9 | "io" 10 | 11 | "golang.org/x/crypto/curve25519" 12 | "golang.org/x/crypto/hkdf" 13 | ) 14 | 15 | type aliceAxolotlParameters struct { 16 | OurIdentityKey *IdentityKeyPair 17 | OurBaseKey *ECKeyPair 18 | 19 | TheirIdentity *IdentityKey 20 | TheirSignedPreKey *ECPublicKey 21 | TheirOneTimePreKey *ECPublicKey 22 | TheirRatchetKey *ECPublicKey 23 | } 24 | 25 | type bobAxolotlParameters struct { 26 | OurIdentityKey *IdentityKeyPair 27 | OurSignedPreKey *ECKeyPair 28 | OurOneTimePreKey *ECKeyPair 29 | OurRatchetKey *ECKeyPair 30 | 31 | TheirBaseKey *ECPublicKey 32 | TheirIdentity *IdentityKey 33 | } 34 | 35 | type rootKey struct { 36 | Key [32]byte 37 | } 38 | 39 | func newRootKey(key []byte) *rootKey { 40 | ensureKeyLength(key) 41 | rk := &rootKey{} 42 | copy(rk.Key[:], key) 43 | return rk 44 | } 45 | 46 | func (r *rootKey) createChain(theirRatchetKey *ECPublicKey, ourRatchetKey *ECKeyPair) (*derivedKeys, error) { 47 | var keyMaterial [32]byte 48 | calculateAgreement(&keyMaterial, theirRatchetKey.Key(), ourRatchetKey.PrivateKey.Key()) 49 | b, err := DeriveSecrets(keyMaterial[:], r.Key[:], []byte("WhisperRatchet"), 64) 50 | if err != nil { 51 | return nil, err 52 | } 53 | dk := &derivedKeys{} 54 | copy(dk.rootKey.Key[:], b[:32]) 55 | copy(dk.chainKey.Key[:], b[32:]) 56 | dk.chainKey.Index = 0 57 | return dk, nil 58 | } 59 | 60 | type chainKey struct { 61 | Key [32]byte 62 | Index uint32 63 | } 64 | 65 | func newChainKey(key []byte, index uint32) *chainKey { 66 | ensureKeyLength(key) 67 | ck := &chainKey{Index: index} 68 | copy(ck.Key[:], key) 69 | return ck 70 | } 71 | 72 | type messageKeys struct { 73 | CipherKey []byte 74 | MacKey []byte 75 | Iv []byte 76 | Index uint32 77 | } 78 | 79 | func newMessageKeys(cipherKey, macKey, iv []byte, index uint32) *messageKeys { 80 | return &messageKeys{ 81 | CipherKey: cipherKey, 82 | MacKey: macKey, 83 | Iv: iv, 84 | Index: index, 85 | } 86 | } 87 | 88 | var ( 89 | messageKeySeed = []byte{1} 90 | chainKeySeed = []byte{2} 91 | ) 92 | 93 | func (c *chainKey) getBaseMaterial(seed []byte) []byte { 94 | m := hmac.New(sha256.New, c.Key[:]) 95 | m.Write(seed) 96 | return m.Sum(nil) 97 | } 98 | 99 | func (c *chainKey) getNextChainKey() *chainKey { 100 | b := c.getBaseMaterial(chainKeySeed) 101 | ck := &chainKey{Index: c.Index + 1} 102 | copy(ck.Key[:], b) 103 | return ck 104 | } 105 | 106 | func (c *chainKey) getMessageKeys() (*messageKeys, error) { 107 | b := c.getBaseMaterial(messageKeySeed) 108 | okm, err := DeriveSecrets(b, nil, []byte("WhisperMessageKeys"), 80) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return &messageKeys{ 113 | CipherKey: okm[:32], 114 | MacKey: okm[32:64], 115 | Iv: okm[64:], 116 | Index: c.Index, 117 | }, nil 118 | } 119 | 120 | type derivedKeys struct { 121 | rootKey rootKey 122 | chainKey chainKey 123 | } 124 | 125 | func calculateDerivedKeys(version byte, keyMaterial []byte) (*derivedKeys, error) { 126 | b, err := DeriveSecrets(keyMaterial, nil, []byte("WhisperText"), 64) 127 | if err != nil { 128 | return nil, err 129 | } 130 | dk := &derivedKeys{} 131 | copy(dk.rootKey.Key[:], b[:32]) 132 | copy(dk.chainKey.Key[:], b[32:]) 133 | dk.chainKey.Index = 0 134 | return dk, nil 135 | } 136 | 137 | // DeriveSecrets derives the requested number of bytes using HKDF, given 138 | // the inputKeyMaterial, salt and the info 139 | func DeriveSecrets(inputKeyMaterial, salt, info []byte, size int) ([]byte, error) { 140 | hkdf := hkdf.New(sha256.New, inputKeyMaterial, salt, info) 141 | 142 | secrets := make([]byte, size) 143 | n, err := io.ReadFull(hkdf, secrets) 144 | if err != nil { 145 | return nil, err 146 | } 147 | if n != size { 148 | return nil, err 149 | } 150 | return secrets, nil 151 | } 152 | 153 | var diversifier = [32]byte{ 154 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 155 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 156 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 157 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 158 | 159 | func calculateAgreement(result, theirPub, ourPriv *[32]byte) { 160 | curve25519.ScalarMult(result, ourPriv, theirPub) 161 | } 162 | 163 | func initializeSenderSession(ss *sessionState, version byte, parameters aliceAxolotlParameters) error { 164 | ss.setSessionVersion(uint32(version)) 165 | ss.setLocalIdentityPublic(¶meters.OurIdentityKey.PublicKey) 166 | ss.setRemoteIdentityPublic(parameters.TheirIdentity) 167 | 168 | result := make([]byte, 0, 32*5) 169 | var sharedKey [32]byte 170 | if version >= 3 { 171 | result = append(result, diversifier[:]...) 172 | } 173 | calculateAgreement(&sharedKey, parameters.TheirSignedPreKey.Key(), parameters.OurIdentityKey.PrivateKey.Key()) 174 | result = append(result, sharedKey[:]...) 175 | calculateAgreement(&sharedKey, parameters.TheirIdentity.Key(), parameters.OurBaseKey.PrivateKey.Key()) 176 | result = append(result, sharedKey[:]...) 177 | calculateAgreement(&sharedKey, parameters.TheirSignedPreKey.Key(), parameters.OurBaseKey.PrivateKey.Key()) 178 | result = append(result, sharedKey[:]...) 179 | 180 | if version >= 3 && parameters.TheirOneTimePreKey != nil { 181 | calculateAgreement(&sharedKey, parameters.TheirOneTimePreKey.Key(), parameters.OurBaseKey.PrivateKey.Key()) 182 | result = append(result, sharedKey[:]...) 183 | } 184 | 185 | dk, err := calculateDerivedKeys(version, result) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | sendingRatchetKey := NewECKeyPair() 191 | sendingChain, err := dk.rootKey.createChain(parameters.TheirRatchetKey, sendingRatchetKey) 192 | if err != nil { 193 | return err 194 | } 195 | 196 | ss.addReceiverChain(parameters.TheirRatchetKey, &sendingChain.chainKey) 197 | ss.setSenderChain(sendingRatchetKey, &sendingChain.chainKey) 198 | ss.setRootKey(&sendingChain.rootKey) 199 | 200 | return nil 201 | } 202 | 203 | func initializeReceiverSession(ss *sessionState, version byte, parameters bobAxolotlParameters) error { 204 | ss.setSessionVersion(uint32(version)) 205 | ss.setLocalIdentityPublic(¶meters.OurIdentityKey.PublicKey) 206 | ss.setRemoteIdentityPublic(parameters.TheirIdentity) 207 | result := make([]byte, 0, 32*5) 208 | var sharedKey [32]byte 209 | if version >= 3 { 210 | result = append(result, diversifier[:]...) 211 | } 212 | calculateAgreement(&sharedKey, parameters.TheirIdentity.Key(), parameters.OurSignedPreKey.PrivateKey.Key()) 213 | result = append(result, sharedKey[:]...) 214 | calculateAgreement(&sharedKey, parameters.TheirBaseKey.Key(), parameters.OurIdentityKey.PrivateKey.Key()) 215 | result = append(result, sharedKey[:]...) 216 | calculateAgreement(&sharedKey, parameters.TheirBaseKey.Key(), parameters.OurSignedPreKey.PrivateKey.Key()) 217 | result = append(result, sharedKey[:]...) 218 | 219 | if version >= 3 && parameters.OurOneTimePreKey != nil { 220 | calculateAgreement(&sharedKey, parameters.TheirBaseKey.Key(), parameters.OurOneTimePreKey.PrivateKey.Key()) 221 | result = append(result, sharedKey[:]...) 222 | } 223 | dk, err := calculateDerivedKeys(version, result) 224 | if err != nil { 225 | return err 226 | } 227 | ss.setSenderChain(parameters.OurRatchetKey, &dk.chainKey) 228 | ss.setRootKey(&dk.rootKey) 229 | return nil 230 | } 231 | -------------------------------------------------------------------------------- /axolotl/store.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package axolotl 5 | 6 | // IdentityStore provides an interface to identity information. 7 | type IdentityStore interface { 8 | GetIdentityKeyPair() (*IdentityKeyPair, error) 9 | GetLocalRegistrationID() (uint32, error) 10 | SaveIdentity(string, *IdentityKey) error 11 | IsTrustedIdentity(string, *IdentityKey) bool 12 | } 13 | 14 | // PreKeyStore provides an interface to accessing the local prekeys. 15 | type PreKeyStore interface { 16 | LoadPreKey(uint32) (*PreKeyRecord, error) 17 | StorePreKey(uint32, *PreKeyRecord) error 18 | ContainsPreKey(uint32) bool 19 | RemovePreKey(uint32) 20 | } 21 | 22 | // SignedPreKeyStore provides an interface to accessing the local signed prekeys. 23 | type SignedPreKeyStore interface { 24 | LoadSignedPreKey(uint32) (*SignedPreKeyRecord, error) 25 | LoadSignedPreKeys() []SignedPreKeyRecord 26 | StoreSignedPreKey(uint32, *SignedPreKeyRecord) error 27 | ContainsSignedPreKey(uint32) bool 28 | RemoveSignedPreKey(uint32) 29 | } 30 | 31 | // SessionStore provides an interface to accessing the local session records. 32 | type SessionStore interface { 33 | Lock() 34 | Unlock() 35 | LoadSession(string, uint32) (*SessionRecord, error) 36 | GetSubDeviceSessions(string) []uint32 37 | StoreSession(string, uint32, *SessionRecord) error 38 | ContainsSession(string, uint32) bool 39 | DeleteSession(string, uint32) 40 | DeleteAllSessions(string) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/textsecure/.config/config.yml: -------------------------------------------------------------------------------- 1 | #Put your phone number that will get verified by the server here 2 | tel: "+1771111001" 3 | 4 | #Server URL 5 | server: https://textsecure-service.whispersystems.org:443 6 | 7 | #Server's TLS root certificate path when using a non-default server 8 | #See https://github.com/janimo/textsecure-docker#using-https 9 | #rootCA: path/to/rootCA.pem 10 | 11 | #proxy URL if you use one 12 | #proxy: http://host:8080 13 | 14 | #Registration code verification can be done via sms or voice 15 | #Additionally the 'dev' type is supported for a non-official server fork. 16 | verificationType: sms 17 | 18 | #Where to store sessions' metadata, by default ".storage" 19 | #storageDir: path/to/storage 20 | 21 | #Setting this to false turns off encryption of local storage, for developing. 22 | #unencryptedStorage: true 23 | 24 | #The local storage uses password base encryption 25 | #If not present here, the password will be requested on startup 26 | storagePassword: password 27 | 28 | #Overrides the default HTTP User-Agent field ("Go 1.1 package http") 29 | #userAgent: "TextSecure Go command line client 0.1" 30 | 31 | #Logging verbosity; valid values are error,warn,info and debug, case insensitive 32 | #loglevel: debug 33 | -------------------------------------------------------------------------------- /cmd/textsecure/.config/contacts.yml: -------------------------------------------------------------------------------- 1 | #Add the contacts you want to reference by name on the app command line here 2 | contacts: 3 | - name: Bob 4 | tel: "+1771111002" 5 | - name: Eve 6 | tel: "+1771111003" 7 | -------------------------------------------------------------------------------- /cmd/textsecure/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net/url" 11 | "os" 12 | "strings" 13 | "time" 14 | 15 | "github.com/janimo/textsecure" 16 | "github.com/janimo/textsecure/axolotl" 17 | "golang.org/x/crypto/ssh/terminal" 18 | ) 19 | 20 | // Simple command line test app for TextSecure. 21 | // It can act as an echo service, send one-off messages and attachments, 22 | // or carry on a conversation with another client 23 | 24 | var ( 25 | echo bool 26 | to string 27 | group bool 28 | message string 29 | attachment string 30 | newgroup string 31 | leavegroup string 32 | endsession bool 33 | showdevices bool 34 | linkdevice string 35 | unlinkdevice int 36 | configDir string 37 | stress int 38 | ) 39 | 40 | func init() { 41 | flag.BoolVar(&echo, "echo", false, "Act as an echo service") 42 | flag.StringVar(&to, "to", "", "Contact name to send the message to") 43 | flag.BoolVar(&group, "group", false, "Destination is a group") 44 | flag.StringVar(&message, "message", "", "Single message to send, then exit") 45 | flag.StringVar(&attachment, "attachment", "", "File to attach") 46 | flag.StringVar(&newgroup, "newgroup", "", "Create a group, the argument has the format 'name:member1:member2'") 47 | flag.StringVar(&leavegroup, "leavegroup", "", "Leave a group named by the argument") 48 | flag.BoolVar(&endsession, "endsession", false, "Terminate session with peer") 49 | flag.BoolVar(&showdevices, "showdevices", false, "Show linked devices") 50 | flag.StringVar(&linkdevice, "linkdevice", "", "Link a new device, the argument is a url in the format 'tsdevice:/?uuid=xxx&pub_key=yyy'") 51 | flag.IntVar(&unlinkdevice, "unlinkdevice", 0, "Unlink a device, the argument is the id of the device to delete") 52 | flag.IntVar(&stress, "stress", 0, "Automatically send many messages to the peer") 53 | flag.StringVar(&configDir, "config", ".config", "Location of config dir") 54 | } 55 | 56 | var ( 57 | red = "\x1b[31m" 58 | green = "\x1b[32m" 59 | yellow = "\x1b[33m" 60 | blue = "\x1b[34m" 61 | ) 62 | 63 | func readLine(prompt string) string { 64 | reader := bufio.NewReader(os.Stdin) 65 | fmt.Print(prompt) 66 | text, _, err := reader.ReadLine() 67 | if err != nil { 68 | log.Fatal("Cannot read line from console: ", err) 69 | } 70 | return string(text) 71 | } 72 | 73 | func getVerificationCode() string { 74 | return readLine("Enter verification code>") 75 | } 76 | 77 | func getStoragePassword() string { 78 | fmt.Printf("Input storage password>") 79 | password, err := terminal.ReadPassword(0) 80 | if err != nil { 81 | log.Fatal(err) 82 | } 83 | fmt.Println() 84 | return string(password) 85 | } 86 | 87 | func getConfig() (*textsecure.Config, error) { 88 | return textsecure.ReadConfig(configDir + "/config.yml") 89 | } 90 | 91 | func getLocalContacts() ([]textsecure.Contact, error) { 92 | return textsecure.ReadContacts(configDir + "/contacts.yml") 93 | } 94 | 95 | func sendMessage(isGroup bool, to, message string) error { 96 | var err error 97 | if isGroup { 98 | _, err = textsecure.SendGroupMessage(to, message) 99 | } else { 100 | _, err = textsecure.SendMessage(to, message) 101 | if nerr, ok := err.(axolotl.NotTrustedError); ok { 102 | log.Fatalf("Peer identity not trusted. Remove the file .storage/identity/remote_%s to approve\n", nerr.ID) 103 | } 104 | } 105 | return err 106 | } 107 | 108 | func sendAttachment(isGroup bool, to, message string, f io.Reader) error { 109 | var err error 110 | if isGroup { 111 | _, err = textsecure.SendGroupAttachment(to, message, f) 112 | } else { 113 | _, err = textsecure.SendAttachment(to, message, f) 114 | if nerr, ok := err.(axolotl.NotTrustedError); ok { 115 | log.Fatalf("Peer identity not trusted. Remove the file .storage/identity/remote_%s to approve\n", nerr.ID) 116 | } 117 | } 118 | return err 119 | } 120 | 121 | // conversationLoop sends messages read from the console 122 | func conversationLoop(isGroup bool) { 123 | for { 124 | message := readLine(fmt.Sprintf("%s>", blue)) 125 | if message == "" { 126 | continue 127 | } 128 | 129 | err := sendMessage(isGroup, to, message) 130 | 131 | if err != nil { 132 | log.Println(err) 133 | } 134 | } 135 | } 136 | 137 | func messageHandler(msg *textsecure.Message) { 138 | if echo { 139 | to := msg.Source() 140 | if msg.Group() != nil { 141 | to = msg.Group().Hexid 142 | } 143 | err := sendMessage(msg.Group() != nil, to, msg.Message()) 144 | 145 | if err != nil { 146 | log.Println(err) 147 | } 148 | return 149 | } 150 | 151 | if msg.Message() != "" { 152 | fmt.Printf("\r %s%s\n>", pretty(msg), blue) 153 | } 154 | 155 | for _, a := range msg.Attachments() { 156 | handleAttachment(msg.Source(), a.R) 157 | } 158 | 159 | // if no peer was specified on the command line, start a conversation with the first one contacting us 160 | if to == "" { 161 | to = msg.Source() 162 | isGroup := false 163 | if msg.Group() != nil { 164 | isGroup = true 165 | to = msg.Group().Hexid 166 | } 167 | go conversationLoop(isGroup) 168 | } 169 | } 170 | 171 | func handleAttachment(src string, r io.Reader) { 172 | f, err := ioutil.TempFile(".", "TextSecure_Attachment") 173 | if err != nil { 174 | log.Println(err) 175 | return 176 | } 177 | l, err := io.Copy(f, r) 178 | if err != nil { 179 | log.Println(err) 180 | return 181 | } 182 | log.Printf("Saving attachment of length %d from %s to %s", l, src, f.Name()) 183 | } 184 | 185 | var timeFormat = "Mon 03:04" 186 | 187 | func timestamp(msg *textsecure.Message) string { 188 | t := time.Unix(0, int64(msg.Timestamp())*1000000) 189 | return t.Format(timeFormat) 190 | } 191 | 192 | func pretty(msg *textsecure.Message) string { 193 | src := getName(msg.Source()) 194 | if msg.Group() != nil { 195 | src = src + "[" + msg.Group().Name + "]" 196 | } 197 | return fmt.Sprintf("%s%s %s%s %s%s", yellow, timestamp(msg), red, src, green, msg.Message()) 198 | } 199 | 200 | // getName returns the local contact name corresponding to a phone number, 201 | // or failing to find a contact the phone number itself 202 | func getName(tel string) string { 203 | if n, ok := telToName[tel]; ok { 204 | return n 205 | } 206 | return tel 207 | } 208 | 209 | func registrationDone() { 210 | log.Println("Registration done.") 211 | } 212 | 213 | var telToName map[string]string 214 | 215 | func main() { 216 | flag.Parse() 217 | log.SetFlags(0) 218 | client := &textsecure.Client{ 219 | GetConfig: getConfig, 220 | GetLocalContacts: getLocalContacts, 221 | GetVerificationCode: getVerificationCode, 222 | GetStoragePassword: getStoragePassword, 223 | MessageHandler: messageHandler, 224 | RegistrationDone: registrationDone, 225 | } 226 | err := textsecure.Setup(client) 227 | if err != nil { 228 | log.Fatal(err) 229 | } 230 | 231 | if linkdevice != "" { 232 | log.Printf("Linking new device with url: %s", linkdevice) 233 | url, err := url.Parse(linkdevice) 234 | if err != nil { 235 | log.Fatal(err) 236 | } 237 | 238 | uuid := url.Query().Get("uuid") 239 | pk := url.Query().Get("pub_key") 240 | code, err := textsecure.NewDeviceVerificationCode() 241 | if err != nil { 242 | log.Fatal(err) 243 | } 244 | 245 | err = textsecure.AddDevice(uuid, pk, code) 246 | if err != nil { 247 | log.Fatal(err) 248 | } 249 | return 250 | } 251 | 252 | if unlinkdevice > 0 { 253 | err = textsecure.UnlinkDevice(unlinkdevice) 254 | if err != nil { 255 | log.Fatal(err) 256 | } 257 | return 258 | } 259 | 260 | if showdevices { 261 | devs, err := textsecure.LinkedDevices() 262 | if err != nil { 263 | log.Fatal(err) 264 | } 265 | 266 | for _, d := range devs { 267 | log.Printf("ID: %d\n", d.ID) 268 | log.Printf("Name: %s\n", d.Name) 269 | log.Printf("Created: %d\n", d.Created) 270 | log.Printf("LastSeen: %d\n", d.LastSeen) 271 | log.Println("============") 272 | } 273 | return 274 | } 275 | 276 | if !echo { 277 | contacts, err := textsecure.GetRegisteredContacts() 278 | if err != nil { 279 | log.Printf("Could not get contacts: %s\n", err) 280 | } 281 | 282 | telToName = make(map[string]string) 283 | for _, c := range contacts { 284 | telToName[c.Tel] = c.Name 285 | } 286 | 287 | if newgroup != "" { 288 | s := strings.Split(newgroup, ":") 289 | textsecure.NewGroup(s[0], s[1:]) 290 | return 291 | } 292 | if leavegroup != "" { 293 | textsecure.LeaveGroup(leavegroup) 294 | return 295 | } 296 | // If "to" matches a contact name then get its phone number, otherwise assume "to" is a phone number 297 | for _, c := range contacts { 298 | if strings.EqualFold(c.Name, to) { 299 | to = c.Tel 300 | break 301 | } 302 | } 303 | 304 | if to != "" { 305 | // Terminate the session with the peer 306 | if endsession { 307 | textsecure.EndSession(to, "TERMINATE") 308 | return 309 | } 310 | 311 | // Send attachment with optional message then exit 312 | if attachment != "" { 313 | f, err := os.Open(attachment) 314 | if err != nil { 315 | log.Fatal(err) 316 | } 317 | 318 | err = sendAttachment(group, to, message, f) 319 | if err != nil { 320 | log.Fatal(err) 321 | } 322 | return 323 | } 324 | 325 | if stress > 0 { 326 | c := make(chan int, stress) 327 | for i := 0; i < stress; i++ { 328 | go func(i int) { 329 | sendMessage(false, to, fmt.Sprintf("Stress %d\n", i)) 330 | c <- i 331 | }(i) 332 | } 333 | for i := 0; i < stress; i++ { 334 | <-c 335 | } 336 | return 337 | } 338 | 339 | // Send a message then exit 340 | if message != "" { 341 | err := sendMessage(group, to, message) 342 | if err != nil { 343 | log.Fatal(err) 344 | } 345 | return 346 | } 347 | 348 | // Enter conversation mode 349 | go conversationLoop(group) 350 | } 351 | } 352 | 353 | err = textsecure.StartListening() 354 | if err != nil { 355 | log.Println(err) 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "io/ioutil" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // Config holds application configuration settings 13 | type Config struct { 14 | Tel string `yaml:"tel"` // Our telephone number 15 | Server string `yaml:"server"` // The TextSecure server URL 16 | RootCA string `yaml:"rootCA"` // The TLS signing certificate of the server we connect to 17 | ProxyServer string `yaml:"proxy"` // HTTP Proxy URL if one is being used 18 | VerificationType string `yaml:"verificationType"` // Code verification method during registration (SMS/VOICE/DEV) 19 | StorageDir string `yaml:"storageDir"` // Directory for the persistent storage 20 | UnencryptedStorage bool `yaml:"unencryptedStorage"` // Whether to store plaintext keys and session state (only for development) 21 | StoragePassword string `yaml:"storagePassword"` // Password to the storage 22 | LogLevel string `yaml:"loglevel"` // Verbosity of the logging messages 23 | UserAgent string `yaml:"userAgent"` // Override for the default HTTP User Agent header field 24 | AlwaysTrustPeerID bool `yaml:"alwaysTrustPeerID"` // Workaround until proper handling of peer reregistering with new ID. 25 | } 26 | 27 | // ReadConfig reads a YAML config file 28 | func ReadConfig(fileName string) (*Config, error) { 29 | b, err := ioutil.ReadFile(fileName) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | cfg := &Config{} 35 | err = yaml.Unmarshal(b, cfg) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return cfg, nil 40 | } 41 | 42 | // WriteConfig saves a config to a file 43 | func WriteConfig(filename string, cfg *Config) error { 44 | b, err := yaml.Marshal(cfg) 45 | if err != nil { 46 | return err 47 | } 48 | return ioutil.WriteFile(filename, b, 0600) 49 | } 50 | 51 | // loadConfig gets the config via the client and makes sure 52 | // that for unset values sane defaults are used 53 | func loadConfig() (*Config, error) { 54 | cfg, err := client.GetConfig() 55 | 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | if cfg.Server == "" { 61 | cfg.Server = "https://textsecure-service.whispersystems.org:443" 62 | } 63 | 64 | if cfg.VerificationType == "" { 65 | cfg.VerificationType = "sms" 66 | } 67 | 68 | if cfg.StorageDir == "" { 69 | cfg.StorageDir = ".storage" 70 | } 71 | 72 | return cfg, nil 73 | } 74 | -------------------------------------------------------------------------------- /contacts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "io/ioutil" 8 | 9 | "gopkg.in/yaml.v2" 10 | ) 11 | 12 | // Contact contains information about a contact. 13 | type Contact struct { 14 | Name string 15 | Tel string 16 | Photo string 17 | } 18 | 19 | type yamlContacts struct { 20 | Contacts []Contact 21 | } 22 | 23 | // ReadContacts reads a YAML contacts file 24 | func ReadContacts(fileName string) ([]Contact, error) { 25 | b, err := ioutil.ReadFile(fileName) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | contacts := &yamlContacts{} 31 | err = yaml.Unmarshal(b, contacts) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return contacts.Contacts, nil 36 | } 37 | 38 | // WriteContacts saves a list of contacts to a file 39 | func WriteContacts(filename string, contacts []Contact) error { 40 | c := &yamlContacts{contacts} 41 | b, err := yaml.Marshal(c) 42 | if err != nil { 43 | return err 44 | } 45 | return ioutil.WriteFile(filename, b, 0600) 46 | } 47 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "bytes" 8 | "crypto/aes" 9 | "crypto/cipher" 10 | "crypto/hmac" 11 | "crypto/rand" 12 | "crypto/sha1" 13 | "crypto/sha256" 14 | "encoding/binary" 15 | "errors" 16 | "fmt" 17 | "io" 18 | 19 | "github.com/golang/protobuf/proto" 20 | "github.com/janimo/textsecure/axolotl" 21 | "github.com/janimo/textsecure/protobuf" 22 | "golang.org/x/crypto/curve25519" 23 | ) 24 | 25 | // randBytes returns a sequence of random bytes from the CSPRNG 26 | func randBytes(data []byte) { 27 | if _, err := io.ReadFull(rand.Reader, data); err != nil { 28 | panic(err) 29 | } 30 | } 31 | 32 | // randUint32 returns a random 32bit uint from the CSPRNG 33 | func randUint32() uint32 { 34 | b := make([]byte, 4) 35 | _, err := rand.Read(b) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return binary.BigEndian.Uint32(b) 40 | } 41 | 42 | // appendMAC returns the given message with a HMAC-SHA256 MAC appended 43 | func appendMAC(key, b []byte) []byte { 44 | m := hmac.New(sha256.New, key) 45 | m.Write(b) 46 | return m.Sum(b) 47 | } 48 | 49 | // verifyMAC verifies a HMAC-SHA256 MAC on a message 50 | func verifyMAC(key, b, mac []byte) bool { 51 | m := hmac.New(sha256.New, key) 52 | m.Write(b) 53 | return hmac.Equal(m.Sum(nil), mac) 54 | } 55 | 56 | // telToToken calculates a truncated SHA1 hash of a phone number, to be used for contact discovery 57 | func telToToken(tel string) string { 58 | s := sha1.Sum([]byte(tel)) 59 | return base64EncWithoutPadding(s[:10]) 60 | } 61 | 62 | // aesEncrypt encrypts the given plaintext under the given key in AES-CBC mode 63 | func aesEncrypt(key, plaintext []byte) ([]byte, error) { 64 | block, err := aes.NewCipher(key) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | pad := aes.BlockSize - len(plaintext)%aes.BlockSize 70 | plaintext = append(plaintext, bytes.Repeat([]byte{byte(pad)}, pad)...) 71 | 72 | ciphertext := make([]byte, len(plaintext)) 73 | iv := make([]byte, 16) 74 | randBytes(iv) 75 | 76 | mode := cipher.NewCBCEncrypter(block, iv) 77 | mode.CryptBlocks(ciphertext, plaintext) 78 | 79 | return append(iv, ciphertext...), nil 80 | } 81 | 82 | // aesDecrypt decrypts the given ciphertext under the given key in AES-CBC mode 83 | func aesDecrypt(key, ciphertext []byte) ([]byte, error) { 84 | block, err := aes.NewCipher(key) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if len(ciphertext)%aes.BlockSize != 0 { 90 | return nil, errors.New("ciphertext not multiple of AES blocksize") 91 | } 92 | 93 | iv := ciphertext[:aes.BlockSize] 94 | mode := cipher.NewCBCDecrypter(block, iv) 95 | mode.CryptBlocks(ciphertext, ciphertext) 96 | pad := ciphertext[len(ciphertext)-1] 97 | if pad > aes.BlockSize { 98 | return nil, fmt.Errorf("pad value (%d) larger than AES blocksize (%d)", pad, aes.BlockSize) 99 | } 100 | return ciphertext[aes.BlockSize : len(ciphertext)-int(pad)], nil 101 | } 102 | 103 | // ProvisioningCipher 104 | func provisioningCipher(pm *textsecure.ProvisionMessage, theirPublicKey *axolotl.ECPublicKey) ([]byte, error) { 105 | ourKeyPair := axolotl.GenerateIdentityKeyPair() 106 | 107 | version := []byte{0x01} 108 | var sharedKey [32]byte 109 | curve25519.ScalarMult(&sharedKey, ourKeyPair.PrivateKey.Key(), theirPublicKey.Key()) 110 | derivedSecret, err := axolotl.DeriveSecrets(sharedKey[:], nil, []byte("TextSecure Provisioning Message"), 64) 111 | 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | aesKey := derivedSecret[:32] 117 | macKey := derivedSecret[32:] 118 | message, err := proto.Marshal(pm) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | ciphertext, err := aesEncrypt(aesKey, message) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | m := hmac.New(sha256.New, macKey) 129 | m.Write(append(version[:], ciphertext[:]...)) 130 | mac := m.Sum(nil) 131 | body := []byte{} 132 | body = append(body, version[:]...) 133 | body = append(body, ciphertext[:]...) 134 | body = append(body, mac[:]...) 135 | 136 | pe := &textsecure.ProvisionEnvelope{ 137 | PublicKey: ourKeyPair.PublicKey.Serialize(), 138 | Body: body, 139 | } 140 | 141 | return proto.Marshal(pe) 142 | } 143 | -------------------------------------------------------------------------------- /crypto_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMAC(t *testing.T) { 13 | key := make([]byte, 32) 14 | randBytes(key) 15 | msg := make([]byte, 100) 16 | randBytes(msg) 17 | macced := appendMAC(key, msg) 18 | assert.True(t, verifyMAC(key, macced[:100], macced[100:])) 19 | } 20 | -------------------------------------------------------------------------------- /curve25519sign/curve25519sign.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | // Package curve25519sign implements a signature scheme based on Curve25519 keys. 5 | // See https://moderncrypto.org/mail-archive/curves/2014/000205.html for details. 6 | package curve25519sign 7 | 8 | import ( 9 | "crypto/sha512" 10 | 11 | "github.com/agl/ed25519" 12 | "github.com/agl/ed25519/edwards25519" 13 | ) 14 | 15 | // Sign signs the message with privateKey and returns a signature. 16 | func Sign(privateKey *[32]byte, message []byte, random [64]byte) *[64]byte { 17 | 18 | // Calculate Ed25519 public key from Curve25519 private key 19 | var A edwards25519.ExtendedGroupElement 20 | var publicKey [32]byte 21 | edwards25519.GeScalarMultBase(&A, privateKey) 22 | A.ToBytes(&publicKey) 23 | 24 | // Calculate r 25 | diversifier := [32]byte{ 26 | 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 27 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 28 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 29 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 30 | 31 | var r [64]byte 32 | h := sha512.New() 33 | h.Write(diversifier[:]) 34 | h.Write(privateKey[:]) 35 | h.Write(message) 36 | h.Write(random[:]) 37 | h.Sum(r[:0]) 38 | 39 | // Calculate R 40 | var rReduced [32]byte 41 | edwards25519.ScReduce(&rReduced, &r) 42 | var R edwards25519.ExtendedGroupElement 43 | edwards25519.GeScalarMultBase(&R, &rReduced) 44 | 45 | var encodedR [32]byte 46 | R.ToBytes(&encodedR) 47 | 48 | // Calculate S = r + SHA2-512(R || A_ed || msg) * a (mod L) 49 | var hramDigest [64]byte 50 | h.Reset() 51 | h.Write(encodedR[:]) 52 | h.Write(publicKey[:]) 53 | h.Write(message) 54 | h.Sum(hramDigest[:0]) 55 | var hramDigestReduced [32]byte 56 | edwards25519.ScReduce(&hramDigestReduced, &hramDigest) 57 | 58 | var s [32]byte 59 | edwards25519.ScMulAdd(&s, &hramDigestReduced, privateKey, &rReduced) 60 | 61 | signature := new([64]byte) 62 | copy(signature[:], encodedR[:]) 63 | copy(signature[32:], s[:]) 64 | signature[63] |= publicKey[31] & 0x80 65 | 66 | return signature 67 | } 68 | 69 | // Verify checks whether the message has a valid signature. 70 | func Verify(publicKey [32]byte, message []byte, signature *[64]byte) bool { 71 | 72 | publicKey[31] &= 0x7F 73 | 74 | /* Convert the Curve25519 public key into an Ed25519 public key. In 75 | particular, convert Curve25519's "montgomery" x-coordinate into an 76 | Ed25519 "edwards" y-coordinate: 77 | 78 | ed_y = (mont_x - 1) / (mont_x + 1) 79 | 80 | NOTE: mont_x=-1 is converted to ed_y=0 since fe_invert is mod-exp 81 | 82 | Then move the sign bit into the pubkey from the signature. 83 | */ 84 | 85 | var edY, one, montX, montXMinusOne, montXPlusOne edwards25519.FieldElement 86 | edwards25519.FeFromBytes(&montX, &publicKey) 87 | edwards25519.FeOne(&one) 88 | edwards25519.FeSub(&montXMinusOne, &montX, &one) 89 | edwards25519.FeAdd(&montXPlusOne, &montX, &one) 90 | edwards25519.FeInvert(&montXPlusOne, &montXPlusOne) 91 | edwards25519.FeMul(&edY, &montXMinusOne, &montXPlusOne) 92 | 93 | var A_ed [32]byte 94 | edwards25519.FeToBytes(&A_ed, &edY) 95 | 96 | A_ed[31] |= signature[63] & 0x80 97 | signature[63] &= 0x7F 98 | 99 | return ed25519.Verify(&A_ed, message, signature) 100 | } 101 | -------------------------------------------------------------------------------- /curve25519sign/curve25519sign_test.go: -------------------------------------------------------------------------------- 1 | package curve25519sign 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "golang.org/x/crypto/curve25519" 10 | ) 11 | 12 | func randBytes(data []byte) { 13 | if _, err := io.ReadFull(rand.Reader, data); err != nil { 14 | panic(err) 15 | } 16 | } 17 | 18 | func TestSign(t *testing.T) { 19 | msg := make([]byte, 200) 20 | 21 | var priv, pub [32]byte 22 | var random [64]byte 23 | 24 | // Test for random values of the keys, nonce and message 25 | for i := 0; i < 100; i++ { 26 | randBytes(priv[:]) 27 | priv[0] &= 248 28 | priv[31] &= 63 29 | priv[31] |= 64 30 | curve25519.ScalarBaseMult(&pub, &priv) 31 | randBytes(random[:]) 32 | randBytes(msg) 33 | sig := Sign(&priv, msg, random) 34 | v := Verify(pub, msg, sig) 35 | assert.True(t, v, "Verify must work") 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /groups.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/janimo/textsecure/protobuf" 16 | "gopkg.in/yaml.v2" 17 | ) 18 | 19 | // Group holds group metadata. 20 | type Group struct { 21 | ID []byte 22 | Hexid string 23 | Flags uint32 24 | Name string 25 | Members []string 26 | Avatar io.Reader `yaml:"-"` 27 | } 28 | 29 | var ( 30 | groupDir string 31 | groups = map[string]*Group{} 32 | ) 33 | 34 | // idToHex returns the hex representation of the group id byte-slice 35 | // to be used as both keys in the map and for naming the files. 36 | func idToHex(id []byte) string { 37 | return hex.EncodeToString(id) 38 | } 39 | 40 | // idToPath returns the path of the file for storing a group's state 41 | func idToPath(hexid string) string { 42 | return filepath.Join(groupDir, hexid) 43 | } 44 | 45 | // FIXME: for now using unencrypted YAML files for group state, 46 | // should be definitely encrypted and maybe another format. 47 | 48 | // saveGroup stores a group's state in a file. 49 | func saveGroup(hexid string) error { 50 | b, err := yaml.Marshal(groups[hexid]) 51 | if err != nil { 52 | return err 53 | } 54 | return ioutil.WriteFile(idToPath(hexid), b, 0600) 55 | } 56 | 57 | // loadGroup loads a group's state from a file. 58 | func loadGroup(path string) error { 59 | _, hexid := filepath.Split(path) 60 | b, err := ioutil.ReadFile(path) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | group := &Group{} 66 | err = yaml.Unmarshal(b, group) 67 | if err != nil { 68 | return err 69 | } 70 | groups[hexid] = group 71 | return nil 72 | } 73 | 74 | // setupGroups reads all groups' state from storage. 75 | func setupGroups() error { 76 | groupDir = filepath.Join(config.StorageDir, "groups") 77 | if err := os.MkdirAll(groupDir, 0700); err != nil { 78 | return err 79 | } 80 | filepath.Walk(groupDir, func(path string, fi os.FileInfo, err error) error { 81 | if !fi.IsDir() { 82 | if !strings.Contains(path, "avatar") { 83 | loadGroup(path) 84 | } 85 | } 86 | return nil 87 | 88 | }) 89 | return nil 90 | } 91 | 92 | // removeMember removes a given number from a list. 93 | func removeMember(tel string, members []string) []string { 94 | for i, m := range members { 95 | if m == tel { 96 | members = append(members[:i], members[i+1:]...) 97 | break 98 | } 99 | } 100 | return members 101 | } 102 | 103 | // updateGroup updates a group's state based on an incoming message. 104 | func updateGroup(gr *textsecure.GroupContext) error { 105 | hexid := idToHex(gr.GetId()) 106 | 107 | var r io.Reader 108 | 109 | if av := gr.GetAvatar(); av != nil { 110 | att, err := handleSingleAttachment(av) 111 | if err != nil { 112 | return err 113 | } 114 | r = att.R 115 | } 116 | 117 | groups[hexid] = &Group{ 118 | ID: gr.GetId(), 119 | Hexid: hexid, 120 | Name: gr.GetName(), 121 | Members: gr.GetMembers(), 122 | Avatar: r, 123 | } 124 | return saveGroup(hexid) 125 | } 126 | 127 | // UnknownGroupIDError is returned when an unknown group id is encountered 128 | type UnknownGroupIDError struct { 129 | id string 130 | } 131 | 132 | func (err UnknownGroupIDError) Error() string { 133 | return fmt.Sprintf("unknown group ID %s", err.id) 134 | } 135 | 136 | // quitGroup removes a quitting member from the local group state. 137 | func quitGroup(src string, hexid string) error { 138 | gr, ok := groups[hexid] 139 | if !ok { 140 | return UnknownGroupIDError{hexid} 141 | } 142 | 143 | gr.Members = removeMember(src, gr.Members) 144 | 145 | return saveGroup(hexid) 146 | } 147 | 148 | // GroupUpdateFlag signals that this message updates the group membership or name. 149 | var GroupUpdateFlag uint32 = 1 150 | 151 | // GroupLeavelag signals that this message is a group leave message 152 | var GroupLeaveFlag uint32 = 2 153 | 154 | // handleGroups is the main entry point for handling the group metadata on messages. 155 | func handleGroups(src string, dm *textsecure.DataMessage) (*Group, error) { 156 | gr := dm.GetGroup() 157 | if gr == nil { 158 | return nil, nil 159 | } 160 | hexid := idToHex(gr.GetId()) 161 | 162 | switch gr.GetType() { 163 | case textsecure.GroupContext_UPDATE: 164 | if err := updateGroup(gr); err != nil { 165 | return nil, err 166 | } 167 | groups[hexid].Flags = GroupUpdateFlag 168 | case textsecure.GroupContext_DELIVER: 169 | if _, ok := groups[hexid]; !ok { 170 | return nil, UnknownGroupIDError{hexid} 171 | } 172 | groups[hexid].Flags = 0 173 | case textsecure.GroupContext_QUIT: 174 | if err := quitGroup(src, hexid); err != nil { 175 | return nil, err 176 | } 177 | groups[hexid].Flags = GroupLeaveFlag 178 | } 179 | 180 | return groups[hexid], nil 181 | } 182 | 183 | type groupMessage struct { 184 | id []byte 185 | name string 186 | members []string 187 | typ textsecure.GroupContext_Type 188 | } 189 | 190 | func sendGroupHelper(hexid string, msg string, a *att) (uint64, error) { 191 | var ts uint64 192 | var err error 193 | g, ok := groups[hexid] 194 | if !ok { 195 | return 0, UnknownGroupIDError{hexid} 196 | } 197 | for _, m := range g.Members { 198 | if m != config.Tel { 199 | omsg := &outgoingMessage{ 200 | tel: m, 201 | msg: msg, 202 | attachment: a, 203 | group: &groupMessage{ 204 | id: g.ID, 205 | typ: textsecure.GroupContext_DELIVER, 206 | }, 207 | } 208 | ts, err = sendMessage(omsg) 209 | if err != nil { 210 | return 0, err 211 | } 212 | } 213 | } 214 | return ts, nil 215 | } 216 | 217 | // SendGroupMessage sends a text message to a given group. 218 | func SendGroupMessage(hexid string, msg string) (uint64, error) { 219 | return sendGroupHelper(hexid, msg, nil) 220 | } 221 | 222 | // SendGroupAttachment sends an attachment to a given group. 223 | func SendGroupAttachment(hexid string, msg string, r io.Reader) (uint64, error) { 224 | ct, r := MIMETypeFromReader(r) 225 | a, err := uploadAttachment(r, ct) 226 | if err != nil { 227 | return 0, err 228 | } 229 | return sendGroupHelper(hexid, msg, a) 230 | } 231 | 232 | func newGroupID() []byte { 233 | id := make([]byte, 16) 234 | randBytes(id) 235 | return id 236 | } 237 | 238 | func newGroup(name string, members []string) (*Group, error) { 239 | id := newGroupID() 240 | hexid := idToHex(id) 241 | groups[hexid] = &Group{ 242 | ID: id, 243 | Hexid: hexid, 244 | Name: name, 245 | Members: append(members, config.Tel), 246 | } 247 | err := saveGroup(hexid) 248 | if err != nil { 249 | return nil, err 250 | } 251 | return groups[hexid], nil 252 | } 253 | 254 | func changeGroup(hexid, name string, members []string) (*Group, error) { 255 | g, ok := groups[hexid] 256 | if !ok { 257 | return nil, UnknownGroupIDError{hexid} 258 | } 259 | 260 | g.Name = name 261 | g.Members = append(members, config.Tel) 262 | saveGroup(hexid) 263 | 264 | return g, nil 265 | } 266 | 267 | func sendUpdate(g *Group) error { 268 | for _, m := range g.Members { 269 | if m != config.Tel { 270 | omsg := &outgoingMessage{ 271 | tel: m, 272 | group: &groupMessage{ 273 | id: g.ID, 274 | name: g.Name, 275 | members: g.Members, 276 | typ: textsecure.GroupContext_UPDATE, 277 | }, 278 | } 279 | _, err := sendMessage(omsg) 280 | if err != nil { 281 | return err 282 | } 283 | } 284 | } 285 | return nil 286 | } 287 | 288 | // NewGroup creates a group and notifies its members. 289 | // Our phone number is automatically added to members. 290 | func NewGroup(name string, members []string) (*Group, error) { 291 | g, err := newGroup(name, members) 292 | if err != nil { 293 | return nil, err 294 | } 295 | return g, sendUpdate(g) 296 | } 297 | 298 | // UpdateGroup updates the group name and/or membership. 299 | // Our phone number is automatically added to members. 300 | func UpdateGroup(hexid, name string, members []string) (*Group, error) { 301 | g, err := changeGroup(hexid, name, members) 302 | if err != nil { 303 | return nil, err 304 | } 305 | return g, sendUpdate(g) 306 | } 307 | 308 | func removeGroup(id []byte) error { 309 | hexid := idToHex(id) 310 | err := os.Remove(idToPath(hexid)) 311 | if err != nil { 312 | return err 313 | } 314 | return nil 315 | } 316 | 317 | // LeaveGroup sends a group quit message to the other members of the given group. 318 | func LeaveGroup(hexid string) error { 319 | g, ok := groups[hexid] 320 | if !ok { 321 | return UnknownGroupIDError{hexid} 322 | } 323 | 324 | for _, m := range g.Members { 325 | if m != config.Tel { 326 | omsg := &outgoingMessage{ 327 | tel: m, 328 | group: &groupMessage{ 329 | id: g.ID, 330 | typ: textsecure.GroupContext_QUIT, 331 | }, 332 | } 333 | _, err := sendMessage(omsg) 334 | if err != nil { 335 | return err 336 | } 337 | } 338 | } 339 | removeGroup(g.ID) 340 | return nil 341 | } 342 | -------------------------------------------------------------------------------- /prekeys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | "github.com/janimo/textsecure/axolotl" 12 | "github.com/janimo/textsecure/curve25519sign" 13 | ) 14 | 15 | type preKeyEntity struct { 16 | ID uint32 `json:"keyId"` 17 | PublicKey string `json:"publicKey"` 18 | } 19 | 20 | type signedPreKeyEntity struct { 21 | ID uint32 `json:"keyId"` 22 | PublicKey string `json:"publicKey"` 23 | Signature string `json:"signature"` 24 | } 25 | 26 | type preKeyState struct { 27 | IdentityKey string `json:"identityKey"` 28 | PreKeys []*preKeyEntity `json:"preKeys"` 29 | LastResortKey *preKeyEntity `json:"lastResortKey"` 30 | SignedPreKey *signedPreKeyEntity `json:"signedPreKey"` 31 | } 32 | 33 | type preKeyResponseItem struct { 34 | DeviceID uint32 `json:"deviceId"` 35 | RegistrationID uint32 `json:"registrationId"` 36 | SignedPreKey *signedPreKeyEntity `json:"signedPreKey"` 37 | PreKey *preKeyEntity `json:"preKey"` 38 | } 39 | 40 | type preKeyResponse struct { 41 | IdentityKey string `json:"identityKey"` 42 | Devices []preKeyResponseItem `json:"devices"` 43 | } 44 | 45 | var preKeys *preKeyState 46 | 47 | func randID() uint32 { 48 | return randUint32() & 0xffffff 49 | } 50 | 51 | func generatepreKeyEntity(record *axolotl.PreKeyRecord) *preKeyEntity { 52 | entity := &preKeyEntity{} 53 | entity.ID = *record.Pkrs.Id 54 | entity.PublicKey = encodeKey(record.Pkrs.PublicKey) 55 | return entity 56 | } 57 | 58 | func generateSignedPreKeyEntity(record *axolotl.SignedPreKeyRecord) *signedPreKeyEntity { 59 | entity := &signedPreKeyEntity{} 60 | entity.ID = *record.Spkrs.Id 61 | entity.PublicKey = encodeKey(record.Spkrs.PublicKey) 62 | entity.Signature = base64EncWithoutPadding(record.Spkrs.Signature) 63 | return entity 64 | } 65 | 66 | var preKeyRecords []*axolotl.PreKeyRecord 67 | 68 | func generatePreKey(id uint32) error { 69 | kp := axolotl.NewECKeyPair() 70 | record := axolotl.NewPreKeyRecord(id, kp) 71 | err := textSecureStore.StorePreKey(id, record) 72 | return err 73 | } 74 | 75 | var signedKey *axolotl.SignedPreKeyRecord 76 | 77 | var lastResortPreKeyID uint32 = 0xFFFFFF 78 | 79 | var preKeyBatchSize = 100 80 | 81 | func getNextPreKeyID() uint32 { 82 | return randID() 83 | } 84 | 85 | func generatePreKeys() error { 86 | if err := os.MkdirAll(textSecureStore.preKeysDir, 0700); err != nil { 87 | return err 88 | } 89 | 90 | startID := getNextPreKeyID() 91 | for i := 0; i < preKeyBatchSize; i++ { 92 | err := generatePreKey(startID + uint32(i)) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | err := generatePreKey(lastResortPreKeyID) 98 | if err != nil { 99 | return err 100 | } 101 | signedKey = generateSignedPreKey() 102 | return nil 103 | } 104 | 105 | func getNextSignedPreKeyID() uint32 { 106 | return randID() 107 | } 108 | 109 | func generateSignedPreKey() *axolotl.SignedPreKeyRecord { 110 | kp := axolotl.NewECKeyPair() 111 | id := getNextSignedPreKeyID() 112 | var random [64]byte 113 | randBytes(random[:]) 114 | priv := identityKey.PrivateKey.Key() 115 | signature := curve25519sign.Sign(priv, kp.PublicKey.Serialize(), random) 116 | record := axolotl.NewSignedPreKeyRecord(id, uint64(time.Now().UnixNano()*1000), kp, signature[:]) 117 | textSecureStore.StoreSignedPreKey(id, record) 118 | return record 119 | } 120 | 121 | func generatePreKeyState() error { 122 | err := loadPreKeys() 123 | if err != nil { 124 | return err 125 | } 126 | preKeys = &preKeyState{} 127 | npkr := len(preKeyRecords) 128 | preKeys.PreKeys = make([]*preKeyEntity, npkr-1) 129 | for i := range preKeys.PreKeys { 130 | preKeys.PreKeys[i] = generatepreKeyEntity(preKeyRecords[i]) 131 | } 132 | preKeys.LastResortKey = generatepreKeyEntity(preKeyRecords[npkr-1]) 133 | preKeys.IdentityKey = base64EncWithoutPadding(identityKey.PublicKey.Serialize()) 134 | preKeys.SignedPreKey = generateSignedPreKeyEntity(signedKey) 135 | return nil 136 | } 137 | 138 | func loadPreKeys() error { 139 | preKeyRecords = []*axolotl.PreKeyRecord{} 140 | count := 0 141 | err := filepath.Walk(textSecureStore.preKeysDir, func(path string, fi os.FileInfo, err error) error { 142 | if !fi.IsDir() { 143 | preKeyRecords = append(preKeyRecords, &axolotl.PreKeyRecord{}) 144 | _, fname := filepath.Split(path) 145 | id, err := filenameToID(fname) 146 | if err != nil { 147 | return err 148 | } 149 | preKeyRecords[count], _ = textSecureStore.LoadPreKey(uint32(id)) 150 | count++ 151 | } 152 | return nil 153 | 154 | }) 155 | return err 156 | } 157 | -------------------------------------------------------------------------------- /protobuf/Provisioning.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: Provisioning.proto 3 | // DO NOT EDIT! 4 | 5 | package textsecure 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type ProvisionEnvelope struct { 17 | PublicKey []byte `protobuf:"bytes,1,opt,name=publicKey" json:"publicKey,omitempty"` 18 | Body []byte `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"` 19 | XXX_unrecognized []byte `json:"-"` 20 | } 21 | 22 | func (m *ProvisionEnvelope) Reset() { *m = ProvisionEnvelope{} } 23 | func (m *ProvisionEnvelope) String() string { return proto.CompactTextString(m) } 24 | func (*ProvisionEnvelope) ProtoMessage() {} 25 | 26 | func (m *ProvisionEnvelope) GetPublicKey() []byte { 27 | if m != nil { 28 | return m.PublicKey 29 | } 30 | return nil 31 | } 32 | 33 | func (m *ProvisionEnvelope) GetBody() []byte { 34 | if m != nil { 35 | return m.Body 36 | } 37 | return nil 38 | } 39 | 40 | type ProvisionMessage struct { 41 | IdentityKeyPublic []byte `protobuf:"bytes,1,opt,name=identityKeyPublic" json:"identityKeyPublic,omitempty"` 42 | IdentityKeyPrivate []byte `protobuf:"bytes,2,opt,name=identityKeyPrivate" json:"identityKeyPrivate,omitempty"` 43 | Number *string `protobuf:"bytes,3,opt,name=number" json:"number,omitempty"` 44 | ProvisioningCode *string `protobuf:"bytes,4,opt,name=provisioningCode" json:"provisioningCode,omitempty"` 45 | XXX_unrecognized []byte `json:"-"` 46 | } 47 | 48 | func (m *ProvisionMessage) Reset() { *m = ProvisionMessage{} } 49 | func (m *ProvisionMessage) String() string { return proto.CompactTextString(m) } 50 | func (*ProvisionMessage) ProtoMessage() {} 51 | 52 | func (m *ProvisionMessage) GetIdentityKeyPublic() []byte { 53 | if m != nil { 54 | return m.IdentityKeyPublic 55 | } 56 | return nil 57 | } 58 | 59 | func (m *ProvisionMessage) GetIdentityKeyPrivate() []byte { 60 | if m != nil { 61 | return m.IdentityKeyPrivate 62 | } 63 | return nil 64 | } 65 | 66 | func (m *ProvisionMessage) GetNumber() string { 67 | if m != nil && m.Number != nil { 68 | return *m.Number 69 | } 70 | return "" 71 | } 72 | 73 | func (m *ProvisionMessage) GetProvisioningCode() string { 74 | if m != nil && m.ProvisioningCode != nil { 75 | return *m.ProvisioningCode 76 | } 77 | return "" 78 | } 79 | 80 | func init() { 81 | } 82 | -------------------------------------------------------------------------------- /protobuf/Provisioning.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.textsecure.internal.push"; 4 | option java_outer_classname = "ProvisioningProtos"; 5 | 6 | message ProvisionEnvelope { 7 | optional bytes publicKey = 1; 8 | optional bytes body = 2; // Encrypted ProvisionMessage 9 | } 10 | 11 | message ProvisionMessage { 12 | optional bytes identityKeyPublic = 1; 13 | optional bytes identityKeyPrivate = 2; 14 | optional string number = 3; 15 | optional string provisioningCode = 4; 16 | } 17 | -------------------------------------------------------------------------------- /protobuf/TextSecure.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: TextSecure.proto 3 | // DO NOT EDIT! 4 | 5 | package textsecure 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type Envelope_Type int32 17 | 18 | const ( 19 | Envelope_UNKNOWN Envelope_Type = 0 20 | Envelope_CIPHERTEXT Envelope_Type = 1 21 | Envelope_KEY_EXCHANGE Envelope_Type = 2 22 | Envelope_PREKEY_BUNDLE Envelope_Type = 3 23 | Envelope_RECEIPT Envelope_Type = 5 24 | ) 25 | 26 | var Envelope_Type_name = map[int32]string{ 27 | 0: "UNKNOWN", 28 | 1: "CIPHERTEXT", 29 | 2: "KEY_EXCHANGE", 30 | 3: "PREKEY_BUNDLE", 31 | 5: "RECEIPT", 32 | } 33 | var Envelope_Type_value = map[string]int32{ 34 | "UNKNOWN": 0, 35 | "CIPHERTEXT": 1, 36 | "KEY_EXCHANGE": 2, 37 | "PREKEY_BUNDLE": 3, 38 | "RECEIPT": 5, 39 | } 40 | 41 | func (x Envelope_Type) Enum() *Envelope_Type { 42 | p := new(Envelope_Type) 43 | *p = x 44 | return p 45 | } 46 | func (x Envelope_Type) String() string { 47 | return proto.EnumName(Envelope_Type_name, int32(x)) 48 | } 49 | func (x Envelope_Type) MarshalJSON() ([]byte, error) { 50 | return json.Marshal(x.String()) 51 | } 52 | func (x *Envelope_Type) UnmarshalJSON(data []byte) error { 53 | value, err := proto.UnmarshalJSONEnum(Envelope_Type_value, data, "Envelope_Type") 54 | if err != nil { 55 | return err 56 | } 57 | *x = Envelope_Type(value) 58 | return nil 59 | } 60 | 61 | type DataMessage_Flags int32 62 | 63 | const ( 64 | DataMessage_END_SESSION DataMessage_Flags = 1 65 | ) 66 | 67 | var DataMessage_Flags_name = map[int32]string{ 68 | 1: "END_SESSION", 69 | } 70 | var DataMessage_Flags_value = map[string]int32{ 71 | "END_SESSION": 1, 72 | } 73 | 74 | func (x DataMessage_Flags) Enum() *DataMessage_Flags { 75 | p := new(DataMessage_Flags) 76 | *p = x 77 | return p 78 | } 79 | func (x DataMessage_Flags) String() string { 80 | return proto.EnumName(DataMessage_Flags_name, int32(x)) 81 | } 82 | func (x DataMessage_Flags) MarshalJSON() ([]byte, error) { 83 | return json.Marshal(x.String()) 84 | } 85 | func (x *DataMessage_Flags) UnmarshalJSON(data []byte) error { 86 | value, err := proto.UnmarshalJSONEnum(DataMessage_Flags_value, data, "DataMessage_Flags") 87 | if err != nil { 88 | return err 89 | } 90 | *x = DataMessage_Flags(value) 91 | return nil 92 | } 93 | 94 | type SyncMessage_Request_Type int32 95 | 96 | const ( 97 | SyncMessage_Request_UNKNOWN SyncMessage_Request_Type = 0 98 | SyncMessage_Request_CONTACTS SyncMessage_Request_Type = 1 99 | SyncMessage_Request_GROUPS SyncMessage_Request_Type = 2 100 | ) 101 | 102 | var SyncMessage_Request_Type_name = map[int32]string{ 103 | 0: "UNKNOWN", 104 | 1: "CONTACTS", 105 | 2: "GROUPS", 106 | } 107 | var SyncMessage_Request_Type_value = map[string]int32{ 108 | "UNKNOWN": 0, 109 | "CONTACTS": 1, 110 | "GROUPS": 2, 111 | } 112 | 113 | func (x SyncMessage_Request_Type) Enum() *SyncMessage_Request_Type { 114 | p := new(SyncMessage_Request_Type) 115 | *p = x 116 | return p 117 | } 118 | func (x SyncMessage_Request_Type) String() string { 119 | return proto.EnumName(SyncMessage_Request_Type_name, int32(x)) 120 | } 121 | func (x SyncMessage_Request_Type) MarshalJSON() ([]byte, error) { 122 | return json.Marshal(x.String()) 123 | } 124 | func (x *SyncMessage_Request_Type) UnmarshalJSON(data []byte) error { 125 | value, err := proto.UnmarshalJSONEnum(SyncMessage_Request_Type_value, data, "SyncMessage_Request_Type") 126 | if err != nil { 127 | return err 128 | } 129 | *x = SyncMessage_Request_Type(value) 130 | return nil 131 | } 132 | 133 | type GroupContext_Type int32 134 | 135 | const ( 136 | GroupContext_UNKNOWN GroupContext_Type = 0 137 | GroupContext_UPDATE GroupContext_Type = 1 138 | GroupContext_DELIVER GroupContext_Type = 2 139 | GroupContext_QUIT GroupContext_Type = 3 140 | ) 141 | 142 | var GroupContext_Type_name = map[int32]string{ 143 | 0: "UNKNOWN", 144 | 1: "UPDATE", 145 | 2: "DELIVER", 146 | 3: "QUIT", 147 | } 148 | var GroupContext_Type_value = map[string]int32{ 149 | "UNKNOWN": 0, 150 | "UPDATE": 1, 151 | "DELIVER": 2, 152 | "QUIT": 3, 153 | } 154 | 155 | func (x GroupContext_Type) Enum() *GroupContext_Type { 156 | p := new(GroupContext_Type) 157 | *p = x 158 | return p 159 | } 160 | func (x GroupContext_Type) String() string { 161 | return proto.EnumName(GroupContext_Type_name, int32(x)) 162 | } 163 | func (x GroupContext_Type) MarshalJSON() ([]byte, error) { 164 | return json.Marshal(x.String()) 165 | } 166 | func (x *GroupContext_Type) UnmarshalJSON(data []byte) error { 167 | value, err := proto.UnmarshalJSONEnum(GroupContext_Type_value, data, "GroupContext_Type") 168 | if err != nil { 169 | return err 170 | } 171 | *x = GroupContext_Type(value) 172 | return nil 173 | } 174 | 175 | type Envelope struct { 176 | Type *Envelope_Type `protobuf:"varint,1,opt,name=type,enum=textsecure.Envelope_Type" json:"type,omitempty"` 177 | Source *string `protobuf:"bytes,2,opt,name=source" json:"source,omitempty"` 178 | SourceDevice *uint32 `protobuf:"varint,7,opt,name=sourceDevice" json:"sourceDevice,omitempty"` 179 | Relay *string `protobuf:"bytes,3,opt,name=relay" json:"relay,omitempty"` 180 | Timestamp *uint64 `protobuf:"varint,5,opt,name=timestamp" json:"timestamp,omitempty"` 181 | LegacyMessage []byte `protobuf:"bytes,6,opt,name=legacyMessage" json:"legacyMessage,omitempty"` 182 | Content []byte `protobuf:"bytes,8,opt,name=content" json:"content,omitempty"` 183 | XXX_unrecognized []byte `json:"-"` 184 | } 185 | 186 | func (m *Envelope) Reset() { *m = Envelope{} } 187 | func (m *Envelope) String() string { return proto.CompactTextString(m) } 188 | func (*Envelope) ProtoMessage() {} 189 | 190 | func (m *Envelope) GetType() Envelope_Type { 191 | if m != nil && m.Type != nil { 192 | return *m.Type 193 | } 194 | return 0 195 | } 196 | 197 | func (m *Envelope) GetSource() string { 198 | if m != nil && m.Source != nil { 199 | return *m.Source 200 | } 201 | return "" 202 | } 203 | 204 | func (m *Envelope) GetSourceDevice() uint32 { 205 | if m != nil && m.SourceDevice != nil { 206 | return *m.SourceDevice 207 | } 208 | return 0 209 | } 210 | 211 | func (m *Envelope) GetRelay() string { 212 | if m != nil && m.Relay != nil { 213 | return *m.Relay 214 | } 215 | return "" 216 | } 217 | 218 | func (m *Envelope) GetTimestamp() uint64 { 219 | if m != nil && m.Timestamp != nil { 220 | return *m.Timestamp 221 | } 222 | return 0 223 | } 224 | 225 | func (m *Envelope) GetLegacyMessage() []byte { 226 | if m != nil { 227 | return m.LegacyMessage 228 | } 229 | return nil 230 | } 231 | 232 | func (m *Envelope) GetContent() []byte { 233 | if m != nil { 234 | return m.Content 235 | } 236 | return nil 237 | } 238 | 239 | type Content struct { 240 | DataMessage *DataMessage `protobuf:"bytes,1,opt,name=dataMessage" json:"dataMessage,omitempty"` 241 | SyncMessage *SyncMessage `protobuf:"bytes,2,opt,name=syncMessage" json:"syncMessage,omitempty"` 242 | XXX_unrecognized []byte `json:"-"` 243 | } 244 | 245 | func (m *Content) Reset() { *m = Content{} } 246 | func (m *Content) String() string { return proto.CompactTextString(m) } 247 | func (*Content) ProtoMessage() {} 248 | 249 | func (m *Content) GetDataMessage() *DataMessage { 250 | if m != nil { 251 | return m.DataMessage 252 | } 253 | return nil 254 | } 255 | 256 | func (m *Content) GetSyncMessage() *SyncMessage { 257 | if m != nil { 258 | return m.SyncMessage 259 | } 260 | return nil 261 | } 262 | 263 | type DataMessage struct { 264 | Body *string `protobuf:"bytes,1,opt,name=body" json:"body,omitempty"` 265 | Attachments []*AttachmentPointer `protobuf:"bytes,2,rep,name=attachments" json:"attachments,omitempty"` 266 | Group *GroupContext `protobuf:"bytes,3,opt,name=group" json:"group,omitempty"` 267 | Flags *uint32 `protobuf:"varint,4,opt,name=flags" json:"flags,omitempty"` 268 | XXX_unrecognized []byte `json:"-"` 269 | } 270 | 271 | func (m *DataMessage) Reset() { *m = DataMessage{} } 272 | func (m *DataMessage) String() string { return proto.CompactTextString(m) } 273 | func (*DataMessage) ProtoMessage() {} 274 | 275 | func (m *DataMessage) GetBody() string { 276 | if m != nil && m.Body != nil { 277 | return *m.Body 278 | } 279 | return "" 280 | } 281 | 282 | func (m *DataMessage) GetAttachments() []*AttachmentPointer { 283 | if m != nil { 284 | return m.Attachments 285 | } 286 | return nil 287 | } 288 | 289 | func (m *DataMessage) GetGroup() *GroupContext { 290 | if m != nil { 291 | return m.Group 292 | } 293 | return nil 294 | } 295 | 296 | func (m *DataMessage) GetFlags() uint32 { 297 | if m != nil && m.Flags != nil { 298 | return *m.Flags 299 | } 300 | return 0 301 | } 302 | 303 | type SyncMessage struct { 304 | Sent *SyncMessage_Sent `protobuf:"bytes,1,opt,name=sent" json:"sent,omitempty"` 305 | Contacts *SyncMessage_Contacts `protobuf:"bytes,2,opt,name=contacts" json:"contacts,omitempty"` 306 | Groups *SyncMessage_Groups `protobuf:"bytes,3,opt,name=groups" json:"groups,omitempty"` 307 | Request *SyncMessage_Request `protobuf:"bytes,4,opt,name=request" json:"request,omitempty"` 308 | Read []*SyncMessage_Read `protobuf:"bytes,5,rep,name=read" json:"read,omitempty"` 309 | XXX_unrecognized []byte `json:"-"` 310 | } 311 | 312 | func (m *SyncMessage) Reset() { *m = SyncMessage{} } 313 | func (m *SyncMessage) String() string { return proto.CompactTextString(m) } 314 | func (*SyncMessage) ProtoMessage() {} 315 | 316 | func (m *SyncMessage) GetSent() *SyncMessage_Sent { 317 | if m != nil { 318 | return m.Sent 319 | } 320 | return nil 321 | } 322 | 323 | func (m *SyncMessage) GetContacts() *SyncMessage_Contacts { 324 | if m != nil { 325 | return m.Contacts 326 | } 327 | return nil 328 | } 329 | 330 | func (m *SyncMessage) GetGroups() *SyncMessage_Groups { 331 | if m != nil { 332 | return m.Groups 333 | } 334 | return nil 335 | } 336 | 337 | func (m *SyncMessage) GetRequest() *SyncMessage_Request { 338 | if m != nil { 339 | return m.Request 340 | } 341 | return nil 342 | } 343 | 344 | func (m *SyncMessage) GetRead() []*SyncMessage_Read { 345 | if m != nil { 346 | return m.Read 347 | } 348 | return nil 349 | } 350 | 351 | type SyncMessage_Sent struct { 352 | Destination *string `protobuf:"bytes,1,opt,name=destination" json:"destination,omitempty"` 353 | Timestamp *uint64 `protobuf:"varint,2,opt,name=timestamp" json:"timestamp,omitempty"` 354 | Message *DataMessage `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` 355 | XXX_unrecognized []byte `json:"-"` 356 | } 357 | 358 | func (m *SyncMessage_Sent) Reset() { *m = SyncMessage_Sent{} } 359 | func (m *SyncMessage_Sent) String() string { return proto.CompactTextString(m) } 360 | func (*SyncMessage_Sent) ProtoMessage() {} 361 | 362 | func (m *SyncMessage_Sent) GetDestination() string { 363 | if m != nil && m.Destination != nil { 364 | return *m.Destination 365 | } 366 | return "" 367 | } 368 | 369 | func (m *SyncMessage_Sent) GetTimestamp() uint64 { 370 | if m != nil && m.Timestamp != nil { 371 | return *m.Timestamp 372 | } 373 | return 0 374 | } 375 | 376 | func (m *SyncMessage_Sent) GetMessage() *DataMessage { 377 | if m != nil { 378 | return m.Message 379 | } 380 | return nil 381 | } 382 | 383 | type SyncMessage_Contacts struct { 384 | Blob *AttachmentPointer `protobuf:"bytes,1,opt,name=blob" json:"blob,omitempty"` 385 | XXX_unrecognized []byte `json:"-"` 386 | } 387 | 388 | func (m *SyncMessage_Contacts) Reset() { *m = SyncMessage_Contacts{} } 389 | func (m *SyncMessage_Contacts) String() string { return proto.CompactTextString(m) } 390 | func (*SyncMessage_Contacts) ProtoMessage() {} 391 | 392 | func (m *SyncMessage_Contacts) GetBlob() *AttachmentPointer { 393 | if m != nil { 394 | return m.Blob 395 | } 396 | return nil 397 | } 398 | 399 | type SyncMessage_Groups struct { 400 | Blob *AttachmentPointer `protobuf:"bytes,1,opt,name=blob" json:"blob,omitempty"` 401 | XXX_unrecognized []byte `json:"-"` 402 | } 403 | 404 | func (m *SyncMessage_Groups) Reset() { *m = SyncMessage_Groups{} } 405 | func (m *SyncMessage_Groups) String() string { return proto.CompactTextString(m) } 406 | func (*SyncMessage_Groups) ProtoMessage() {} 407 | 408 | func (m *SyncMessage_Groups) GetBlob() *AttachmentPointer { 409 | if m != nil { 410 | return m.Blob 411 | } 412 | return nil 413 | } 414 | 415 | type SyncMessage_Request struct { 416 | Type *SyncMessage_Request_Type `protobuf:"varint,1,opt,name=type,enum=textsecure.SyncMessage_Request_Type" json:"type,omitempty"` 417 | XXX_unrecognized []byte `json:"-"` 418 | } 419 | 420 | func (m *SyncMessage_Request) Reset() { *m = SyncMessage_Request{} } 421 | func (m *SyncMessage_Request) String() string { return proto.CompactTextString(m) } 422 | func (*SyncMessage_Request) ProtoMessage() {} 423 | 424 | func (m *SyncMessage_Request) GetType() SyncMessage_Request_Type { 425 | if m != nil && m.Type != nil { 426 | return *m.Type 427 | } 428 | return 0 429 | } 430 | 431 | type SyncMessage_Read struct { 432 | Sender *string `protobuf:"bytes,1,opt,name=sender" json:"sender,omitempty"` 433 | Timestamp *uint64 `protobuf:"varint,2,opt,name=timestamp" json:"timestamp,omitempty"` 434 | XXX_unrecognized []byte `json:"-"` 435 | } 436 | 437 | func (m *SyncMessage_Read) Reset() { *m = SyncMessage_Read{} } 438 | func (m *SyncMessage_Read) String() string { return proto.CompactTextString(m) } 439 | func (*SyncMessage_Read) ProtoMessage() {} 440 | 441 | func (m *SyncMessage_Read) GetSender() string { 442 | if m != nil && m.Sender != nil { 443 | return *m.Sender 444 | } 445 | return "" 446 | } 447 | 448 | func (m *SyncMessage_Read) GetTimestamp() uint64 { 449 | if m != nil && m.Timestamp != nil { 450 | return *m.Timestamp 451 | } 452 | return 0 453 | } 454 | 455 | type AttachmentPointer struct { 456 | Id *uint64 `protobuf:"fixed64,1,opt,name=id" json:"id,omitempty"` 457 | ContentType *string `protobuf:"bytes,2,opt,name=contentType" json:"contentType,omitempty"` 458 | Key []byte `protobuf:"bytes,3,opt,name=key" json:"key,omitempty"` 459 | Size *uint32 `protobuf:"varint,4,opt,name=size" json:"size,omitempty"` 460 | Thumbnail []byte `protobuf:"bytes,5,opt,name=thumbnail" json:"thumbnail,omitempty"` 461 | XXX_unrecognized []byte `json:"-"` 462 | } 463 | 464 | func (m *AttachmentPointer) Reset() { *m = AttachmentPointer{} } 465 | func (m *AttachmentPointer) String() string { return proto.CompactTextString(m) } 466 | func (*AttachmentPointer) ProtoMessage() {} 467 | 468 | func (m *AttachmentPointer) GetId() uint64 { 469 | if m != nil && m.Id != nil { 470 | return *m.Id 471 | } 472 | return 0 473 | } 474 | 475 | func (m *AttachmentPointer) GetContentType() string { 476 | if m != nil && m.ContentType != nil { 477 | return *m.ContentType 478 | } 479 | return "" 480 | } 481 | 482 | func (m *AttachmentPointer) GetKey() []byte { 483 | if m != nil { 484 | return m.Key 485 | } 486 | return nil 487 | } 488 | 489 | func (m *AttachmentPointer) GetSize() uint32 { 490 | if m != nil && m.Size != nil { 491 | return *m.Size 492 | } 493 | return 0 494 | } 495 | 496 | func (m *AttachmentPointer) GetThumbnail() []byte { 497 | if m != nil { 498 | return m.Thumbnail 499 | } 500 | return nil 501 | } 502 | 503 | type GroupContext struct { 504 | Id []byte `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` 505 | Type *GroupContext_Type `protobuf:"varint,2,opt,name=type,enum=textsecure.GroupContext_Type" json:"type,omitempty"` 506 | Name *string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` 507 | Members []string `protobuf:"bytes,4,rep,name=members" json:"members,omitempty"` 508 | Avatar *AttachmentPointer `protobuf:"bytes,5,opt,name=avatar" json:"avatar,omitempty"` 509 | XXX_unrecognized []byte `json:"-"` 510 | } 511 | 512 | func (m *GroupContext) Reset() { *m = GroupContext{} } 513 | func (m *GroupContext) String() string { return proto.CompactTextString(m) } 514 | func (*GroupContext) ProtoMessage() {} 515 | 516 | func (m *GroupContext) GetId() []byte { 517 | if m != nil { 518 | return m.Id 519 | } 520 | return nil 521 | } 522 | 523 | func (m *GroupContext) GetType() GroupContext_Type { 524 | if m != nil && m.Type != nil { 525 | return *m.Type 526 | } 527 | return 0 528 | } 529 | 530 | func (m *GroupContext) GetName() string { 531 | if m != nil && m.Name != nil { 532 | return *m.Name 533 | } 534 | return "" 535 | } 536 | 537 | func (m *GroupContext) GetMembers() []string { 538 | if m != nil { 539 | return m.Members 540 | } 541 | return nil 542 | } 543 | 544 | func (m *GroupContext) GetAvatar() *AttachmentPointer { 545 | if m != nil { 546 | return m.Avatar 547 | } 548 | return nil 549 | } 550 | 551 | type ContactDetails struct { 552 | Number *string `protobuf:"bytes,1,opt,name=number" json:"number,omitempty"` 553 | Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 554 | Avatar *ContactDetails_Avatar `protobuf:"bytes,3,opt,name=avatar" json:"avatar,omitempty"` 555 | XXX_unrecognized []byte `json:"-"` 556 | } 557 | 558 | func (m *ContactDetails) Reset() { *m = ContactDetails{} } 559 | func (m *ContactDetails) String() string { return proto.CompactTextString(m) } 560 | func (*ContactDetails) ProtoMessage() {} 561 | 562 | func (m *ContactDetails) GetNumber() string { 563 | if m != nil && m.Number != nil { 564 | return *m.Number 565 | } 566 | return "" 567 | } 568 | 569 | func (m *ContactDetails) GetName() string { 570 | if m != nil && m.Name != nil { 571 | return *m.Name 572 | } 573 | return "" 574 | } 575 | 576 | func (m *ContactDetails) GetAvatar() *ContactDetails_Avatar { 577 | if m != nil { 578 | return m.Avatar 579 | } 580 | return nil 581 | } 582 | 583 | type ContactDetails_Avatar struct { 584 | ContentType *string `protobuf:"bytes,1,opt,name=contentType" json:"contentType,omitempty"` 585 | Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"` 586 | XXX_unrecognized []byte `json:"-"` 587 | } 588 | 589 | func (m *ContactDetails_Avatar) Reset() { *m = ContactDetails_Avatar{} } 590 | func (m *ContactDetails_Avatar) String() string { return proto.CompactTextString(m) } 591 | func (*ContactDetails_Avatar) ProtoMessage() {} 592 | 593 | func (m *ContactDetails_Avatar) GetContentType() string { 594 | if m != nil && m.ContentType != nil { 595 | return *m.ContentType 596 | } 597 | return "" 598 | } 599 | 600 | func (m *ContactDetails_Avatar) GetLength() uint32 { 601 | if m != nil && m.Length != nil { 602 | return *m.Length 603 | } 604 | return 0 605 | } 606 | 607 | type GroupDetails struct { 608 | Id []byte `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` 609 | Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 610 | Members []string `protobuf:"bytes,3,rep,name=members" json:"members,omitempty"` 611 | Avatar *GroupDetails_Avatar `protobuf:"bytes,4,opt,name=avatar" json:"avatar,omitempty"` 612 | Active *bool `protobuf:"varint,5,opt,name=active,def=1" json:"active,omitempty"` 613 | XXX_unrecognized []byte `json:"-"` 614 | } 615 | 616 | func (m *GroupDetails) Reset() { *m = GroupDetails{} } 617 | func (m *GroupDetails) String() string { return proto.CompactTextString(m) } 618 | func (*GroupDetails) ProtoMessage() {} 619 | 620 | const Default_GroupDetails_Active bool = true 621 | 622 | func (m *GroupDetails) GetId() []byte { 623 | if m != nil { 624 | return m.Id 625 | } 626 | return nil 627 | } 628 | 629 | func (m *GroupDetails) GetName() string { 630 | if m != nil && m.Name != nil { 631 | return *m.Name 632 | } 633 | return "" 634 | } 635 | 636 | func (m *GroupDetails) GetMembers() []string { 637 | if m != nil { 638 | return m.Members 639 | } 640 | return nil 641 | } 642 | 643 | func (m *GroupDetails) GetAvatar() *GroupDetails_Avatar { 644 | if m != nil { 645 | return m.Avatar 646 | } 647 | return nil 648 | } 649 | 650 | func (m *GroupDetails) GetActive() bool { 651 | if m != nil && m.Active != nil { 652 | return *m.Active 653 | } 654 | return Default_GroupDetails_Active 655 | } 656 | 657 | type GroupDetails_Avatar struct { 658 | ContentType *string `protobuf:"bytes,1,opt,name=contentType" json:"contentType,omitempty"` 659 | Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"` 660 | XXX_unrecognized []byte `json:"-"` 661 | } 662 | 663 | func (m *GroupDetails_Avatar) Reset() { *m = GroupDetails_Avatar{} } 664 | func (m *GroupDetails_Avatar) String() string { return proto.CompactTextString(m) } 665 | func (*GroupDetails_Avatar) ProtoMessage() {} 666 | 667 | func (m *GroupDetails_Avatar) GetContentType() string { 668 | if m != nil && m.ContentType != nil { 669 | return *m.ContentType 670 | } 671 | return "" 672 | } 673 | 674 | func (m *GroupDetails_Avatar) GetLength() uint32 { 675 | if m != nil && m.Length != nil { 676 | return *m.Length 677 | } 678 | return 0 679 | } 680 | 681 | func init() { 682 | proto.RegisterEnum("textsecure.Envelope_Type", Envelope_Type_name, Envelope_Type_value) 683 | proto.RegisterEnum("textsecure.DataMessage_Flags", DataMessage_Flags_name, DataMessage_Flags_value) 684 | proto.RegisterEnum("textsecure.SyncMessage_Request_Type", SyncMessage_Request_Type_name, SyncMessage_Request_Type_value) 685 | proto.RegisterEnum("textsecure.GroupContext_Type", GroupContext_Type_name, GroupContext_Type_value) 686 | } 687 | -------------------------------------------------------------------------------- /protobuf/TextSecure.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.textsecure.internal.push"; 4 | option java_outer_classname = "TextSecureProtos"; 5 | 6 | message Envelope { 7 | enum Type { 8 | UNKNOWN = 0; 9 | CIPHERTEXT = 1; 10 | KEY_EXCHANGE = 2; 11 | PREKEY_BUNDLE = 3; 12 | RECEIPT = 5; 13 | } 14 | 15 | optional Type type = 1; 16 | optional string source = 2; 17 | optional uint32 sourceDevice = 7; 18 | optional string relay = 3; 19 | optional uint64 timestamp = 5; 20 | optional bytes legacyMessage = 6; // Contains an encrypted DataMessage 21 | optional bytes content = 8; // Contains an encrypted Content 22 | } 23 | 24 | message Content { 25 | optional DataMessage dataMessage = 1; 26 | optional SyncMessage syncMessage = 2; 27 | } 28 | 29 | message DataMessage { 30 | enum Flags { 31 | END_SESSION = 1; 32 | } 33 | 34 | optional string body = 1; 35 | repeated AttachmentPointer attachments = 2; 36 | optional GroupContext group = 3; 37 | optional uint32 flags = 4; 38 | } 39 | 40 | message SyncMessage { 41 | message Sent { 42 | optional string destination = 1; 43 | optional uint64 timestamp = 2; 44 | optional DataMessage message = 3; 45 | } 46 | 47 | message Contacts { 48 | optional AttachmentPointer blob = 1; 49 | } 50 | 51 | message Groups { 52 | optional AttachmentPointer blob = 1; 53 | } 54 | 55 | message Request { 56 | enum Type { 57 | UNKNOWN = 0; 58 | CONTACTS = 1; 59 | GROUPS = 2; 60 | } 61 | 62 | optional Type type = 1; 63 | } 64 | 65 | message Read { 66 | optional string sender = 1; 67 | optional uint64 timestamp = 2; 68 | } 69 | 70 | optional Sent sent = 1; 71 | optional Contacts contacts = 2; 72 | optional Groups groups = 3; 73 | optional Request request = 4; 74 | repeated Read read = 5; 75 | } 76 | 77 | message AttachmentPointer { 78 | optional fixed64 id = 1; 79 | optional string contentType = 2; 80 | optional bytes key = 3; 81 | optional uint32 size = 4; 82 | optional bytes thumbnail = 5; 83 | } 84 | 85 | message GroupContext { 86 | enum Type { 87 | UNKNOWN = 0; 88 | UPDATE = 1; 89 | DELIVER = 2; 90 | QUIT = 3; 91 | } 92 | optional bytes id = 1; 93 | optional Type type = 2; 94 | optional string name = 3; 95 | repeated string members = 4; 96 | optional AttachmentPointer avatar = 5; 97 | } 98 | 99 | message ContactDetails { 100 | message Avatar { 101 | optional string contentType = 1; 102 | optional uint32 length = 2; 103 | } 104 | 105 | optional string number = 1; 106 | optional string name = 2; 107 | optional Avatar avatar = 3; 108 | } 109 | 110 | message GroupDetails { 111 | message Avatar { 112 | optional string contentType = 1; 113 | optional uint32 length = 2; 114 | } 115 | 116 | optional bytes id = 1; 117 | optional string name = 2; 118 | repeated string members = 3; 119 | optional Avatar avatar = 4; 120 | optional bool active = 5 [default = true]; 121 | } 122 | -------------------------------------------------------------------------------- /protobuf/WebSocketResources.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: WebSocketResources.proto 3 | // DO NOT EDIT! 4 | 5 | package textsecure 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import json "encoding/json" 9 | import math "math" 10 | 11 | // Reference proto, json, and math imports to suppress error if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = &json.SyntaxError{} 14 | var _ = math.Inf 15 | 16 | type WebSocketMessage_Type int32 17 | 18 | const ( 19 | WebSocketMessage_UNKNOWN WebSocketMessage_Type = 0 20 | WebSocketMessage_REQUEST WebSocketMessage_Type = 1 21 | WebSocketMessage_RESPONSE WebSocketMessage_Type = 2 22 | ) 23 | 24 | var WebSocketMessage_Type_name = map[int32]string{ 25 | 0: "UNKNOWN", 26 | 1: "REQUEST", 27 | 2: "RESPONSE", 28 | } 29 | var WebSocketMessage_Type_value = map[string]int32{ 30 | "UNKNOWN": 0, 31 | "REQUEST": 1, 32 | "RESPONSE": 2, 33 | } 34 | 35 | func (x WebSocketMessage_Type) Enum() *WebSocketMessage_Type { 36 | p := new(WebSocketMessage_Type) 37 | *p = x 38 | return p 39 | } 40 | func (x WebSocketMessage_Type) String() string { 41 | return proto.EnumName(WebSocketMessage_Type_name, int32(x)) 42 | } 43 | func (x WebSocketMessage_Type) MarshalJSON() ([]byte, error) { 44 | return json.Marshal(x.String()) 45 | } 46 | func (x *WebSocketMessage_Type) UnmarshalJSON(data []byte) error { 47 | value, err := proto.UnmarshalJSONEnum(WebSocketMessage_Type_value, data, "WebSocketMessage_Type") 48 | if err != nil { 49 | return err 50 | } 51 | *x = WebSocketMessage_Type(value) 52 | return nil 53 | } 54 | 55 | type WebSocketRequestMessage struct { 56 | Verb *string `protobuf:"bytes,1,opt,name=verb" json:"verb,omitempty"` 57 | Path *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` 58 | Body []byte `protobuf:"bytes,3,opt,name=body" json:"body,omitempty"` 59 | Id *uint64 `protobuf:"varint,4,opt,name=id" json:"id,omitempty"` 60 | XXX_unrecognized []byte `json:"-"` 61 | } 62 | 63 | func (m *WebSocketRequestMessage) Reset() { *m = WebSocketRequestMessage{} } 64 | func (m *WebSocketRequestMessage) String() string { return proto.CompactTextString(m) } 65 | func (*WebSocketRequestMessage) ProtoMessage() {} 66 | 67 | func (m *WebSocketRequestMessage) GetVerb() string { 68 | if m != nil && m.Verb != nil { 69 | return *m.Verb 70 | } 71 | return "" 72 | } 73 | 74 | func (m *WebSocketRequestMessage) GetPath() string { 75 | if m != nil && m.Path != nil { 76 | return *m.Path 77 | } 78 | return "" 79 | } 80 | 81 | func (m *WebSocketRequestMessage) GetBody() []byte { 82 | if m != nil { 83 | return m.Body 84 | } 85 | return nil 86 | } 87 | 88 | func (m *WebSocketRequestMessage) GetId() uint64 { 89 | if m != nil && m.Id != nil { 90 | return *m.Id 91 | } 92 | return 0 93 | } 94 | 95 | type WebSocketResponseMessage struct { 96 | Id *uint64 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 97 | Status *uint32 `protobuf:"varint,2,opt,name=status" json:"status,omitempty"` 98 | Message *string `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` 99 | Body []byte `protobuf:"bytes,4,opt,name=body" json:"body,omitempty"` 100 | XXX_unrecognized []byte `json:"-"` 101 | } 102 | 103 | func (m *WebSocketResponseMessage) Reset() { *m = WebSocketResponseMessage{} } 104 | func (m *WebSocketResponseMessage) String() string { return proto.CompactTextString(m) } 105 | func (*WebSocketResponseMessage) ProtoMessage() {} 106 | 107 | func (m *WebSocketResponseMessage) GetId() uint64 { 108 | if m != nil && m.Id != nil { 109 | return *m.Id 110 | } 111 | return 0 112 | } 113 | 114 | func (m *WebSocketResponseMessage) GetStatus() uint32 { 115 | if m != nil && m.Status != nil { 116 | return *m.Status 117 | } 118 | return 0 119 | } 120 | 121 | func (m *WebSocketResponseMessage) GetMessage() string { 122 | if m != nil && m.Message != nil { 123 | return *m.Message 124 | } 125 | return "" 126 | } 127 | 128 | func (m *WebSocketResponseMessage) GetBody() []byte { 129 | if m != nil { 130 | return m.Body 131 | } 132 | return nil 133 | } 134 | 135 | type WebSocketMessage struct { 136 | Type *WebSocketMessage_Type `protobuf:"varint,1,opt,name=type,enum=textsecure.WebSocketMessage_Type" json:"type,omitempty"` 137 | Request *WebSocketRequestMessage `protobuf:"bytes,2,opt,name=request" json:"request,omitempty"` 138 | Response *WebSocketResponseMessage `protobuf:"bytes,3,opt,name=response" json:"response,omitempty"` 139 | XXX_unrecognized []byte `json:"-"` 140 | } 141 | 142 | func (m *WebSocketMessage) Reset() { *m = WebSocketMessage{} } 143 | func (m *WebSocketMessage) String() string { return proto.CompactTextString(m) } 144 | func (*WebSocketMessage) ProtoMessage() {} 145 | 146 | func (m *WebSocketMessage) GetType() WebSocketMessage_Type { 147 | if m != nil && m.Type != nil { 148 | return *m.Type 149 | } 150 | return 0 151 | } 152 | 153 | func (m *WebSocketMessage) GetRequest() *WebSocketRequestMessage { 154 | if m != nil { 155 | return m.Request 156 | } 157 | return nil 158 | } 159 | 160 | func (m *WebSocketMessage) GetResponse() *WebSocketResponseMessage { 161 | if m != nil { 162 | return m.Response 163 | } 164 | return nil 165 | } 166 | 167 | func init() { 168 | proto.RegisterEnum("textsecure.WebSocketMessage_Type", WebSocketMessage_Type_name, WebSocketMessage_Type_value) 169 | } 170 | -------------------------------------------------------------------------------- /protobuf/WebSocketResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2015 Open WhisperSystems 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | */ 17 | package textsecure; 18 | 19 | option java_package = "org.whispersystems.textsecure.internal.websocket"; 20 | option java_outer_classname = "WebSocketProtos"; 21 | 22 | message WebSocketRequestMessage { 23 | optional string verb = 1; 24 | optional string path = 2; 25 | optional bytes body = 3; 26 | optional uint64 id = 4; 27 | } 28 | 29 | message WebSocketResponseMessage { 30 | optional uint64 id = 1; 31 | optional uint32 status = 2; 32 | optional string message = 3; 33 | optional bytes body = 4; 34 | } 35 | 36 | message WebSocketMessage { 37 | enum Type { 38 | UNKNOWN = 0; 39 | REQUEST = 1; 40 | RESPONSE = 2; 41 | } 42 | 43 | optional Type type = 1; 44 | optional WebSocketRequestMessage request = 2; 45 | optional WebSocketResponseMessage response = 3; 46 | } -------------------------------------------------------------------------------- /rootca.go: -------------------------------------------------------------------------------- 1 | package textsecure 2 | 3 | import ( 4 | "crypto/x509" 5 | "io/ioutil" 6 | 7 | log "github.com/Sirupsen/logrus" 8 | ) 9 | 10 | // rootPEM is the PEM formatted signing certificate of the Open Whisper Systems 11 | // server to be used by the TLS client to verify its authenticity instead of 12 | // relying on the system-wide set of root certificates. 13 | var rootPEM = ` 14 | -----BEGIN CERTIFICATE----- 15 | MIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJVUzET 16 | MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEdMBsGA1UECgwUT3Bl 17 | biBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9wZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQD 18 | DApUZXh0U2VjdXJlMB4XDTEzMDMyNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYT 19 | AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQK 20 | DBRPcGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxEzAR 21 | BgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBSWBpOCBD 22 | F0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvfPo863i6Crq1KDxHpB36EwzVcjwLkFTIM 23 | eo7t9s1FQolAt3mErV2U0vie6Ves+yj6grSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADy 24 | ps5B+Zmqcgf653TXS5/0IPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbte 25 | LtVgwBm9R5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4jb69 26 | vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjxP/s5GURuhYa+lGUy 27 | pzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8kDAMBgNVHRMEBTADAQH/MA0GCSqG 28 | SIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1RK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pP 29 | OU6JjIxnrD1XD/EVmTTaTVY5iOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA 30 | 1IMIo3J/s2WF/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe 31 | /oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en4DGXRaLMPRwj 32 | ELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLErwLV 33 | -----END CERTIFICATE----- 34 | ` 35 | 36 | var rootCA *x509.CertPool 37 | 38 | func setupCA() { 39 | pem := []byte(rootPEM) 40 | if config.RootCA != "" && exists(config.RootCA) { 41 | b, err := ioutil.ReadFile(config.RootCA) 42 | if err != nil { 43 | log.Error(err) 44 | return 45 | } 46 | pem = b 47 | } 48 | 49 | rootCA = x509.NewCertPool() 50 | if !rootCA.AppendCertsFromPEM(pem) { 51 | log.Error("Cannot load PEM") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "encoding/base64" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "strconv" 13 | "strings" 14 | "time" 15 | 16 | "github.com/golang/protobuf/proto" 17 | "github.com/janimo/textsecure/axolotl" 18 | "github.com/janimo/textsecure/protobuf" 19 | 20 | log "github.com/Sirupsen/logrus" 21 | ) 22 | 23 | var ( 24 | createAccountPath = "/v1/accounts/%s/code/%s" 25 | verifyAccountPath = "/v1/accounts/code/%s" 26 | registerUPSAccountPath = "/v1/accounts/ups/" 27 | 28 | prekeyMetadataPath = "/v2/keys/" 29 | prekeyPath = "/v2/keys/%s" 30 | prekeyDevicePath = "/v2/keys/%s/%s" 31 | signedPrekeyPath = "/v2/keys/signed" 32 | 33 | provisioningCodePath = "/v1/devices/provisioning/code" 34 | provisioningMessagePath = "/v1/provisioning/%s" 35 | devicePath = "/v1/devices/%s" 36 | 37 | directoryTokensPath = "/v1/directory/tokens" 38 | directoryVerifyPath = "/v1/directory/%s" 39 | messagePath = "/v1/messages/%s" 40 | acknowledgeMessagePath = "/v1/messages/%s/%d" 41 | receiptPath = "/v1/receipt/%s/%d" 42 | allocateAttachmentPath = "/v1/attachments/" 43 | attachmentPath = "/v1/attachments/%d" 44 | ) 45 | 46 | // RegistrationInfo holds the data required to be identified by and 47 | // to communicate with the push server. 48 | // The data is generated once at install time and stored locally. 49 | type RegistrationInfo struct { 50 | password string 51 | registrationID uint32 52 | signalingKey []byte 53 | } 54 | 55 | var registrationInfo RegistrationInfo 56 | 57 | // Registration 58 | 59 | func requestCode(tel, method string) (string, error) { 60 | resp, err := transport.get(fmt.Sprintf(createAccountPath, method, tel)) 61 | if err != nil { 62 | return "", err 63 | } 64 | defer resp.Body.Close() 65 | // unofficial dev method, useful for development, with no telephony account needed on the server 66 | if method == "dev" { 67 | code := make([]byte, 7) 68 | l, err := resp.Body.Read(code) 69 | if err == nil || (err == io.EOF && l == 7) { 70 | return string(code[:3]) + string(code[4:]), nil 71 | } 72 | return "", err 73 | } 74 | return "", nil 75 | } 76 | 77 | type verificationData struct { 78 | SignalingKey string `json:"signalingKey"` 79 | RegistrationID uint32 `json:"registrationId"` 80 | FetchesMessages bool `json:"fetchesMessages"` 81 | } 82 | 83 | func verifyCode(code string) error { 84 | code = strings.Replace(code, "-", "", -1) 85 | vd := verificationData{ 86 | SignalingKey: base64.StdEncoding.EncodeToString(registrationInfo.signalingKey), 87 | FetchesMessages: true, 88 | RegistrationID: registrationInfo.registrationID, 89 | } 90 | body, err := json.Marshal(vd) 91 | if err != nil { 92 | return err 93 | } 94 | resp, err := transport.putJSON(fmt.Sprintf(verifyAccountPath, code), body) 95 | if err != nil { 96 | return err 97 | } 98 | if resp.isError() { 99 | return resp 100 | } 101 | return nil 102 | } 103 | 104 | type upsRegistration struct { 105 | UPSRegistrationID string `json:"upsRegistrationId"` 106 | } 107 | 108 | // RegisterWithUPS registers our Ubuntu push client token with the server. 109 | func RegisterWithUPS(token string) error { 110 | reg := upsRegistration{ 111 | UPSRegistrationID: token, 112 | } 113 | body, err := json.Marshal(reg) 114 | if err != nil { 115 | return err 116 | } 117 | resp, err := transport.putJSON(registerUPSAccountPath, body) 118 | if err != nil { 119 | return err 120 | } 121 | if resp.isError() { 122 | return resp 123 | } 124 | return nil 125 | } 126 | 127 | type jsonDeviceCode struct { 128 | VerificationCode string `json:"verificationCode"` 129 | } 130 | 131 | func getNewDeviceVerificationCode() (string, error) { 132 | resp, err := transport.get(provisioningCodePath) 133 | if err != nil { 134 | return "", err 135 | } 136 | dec := json.NewDecoder(resp.Body) 137 | var c jsonDeviceCode 138 | dec.Decode(&c) 139 | return c.VerificationCode, nil 140 | } 141 | 142 | type DeviceInfo struct { 143 | ID uint32 `json:"id"` 144 | Name string `json:"name"` 145 | Created uint64 `json:"created"` 146 | LastSeen uint64 `json:"lastSeen"` 147 | } 148 | 149 | func getLinkedDevices() ([]DeviceInfo, error) { 150 | type jsonDevices struct { 151 | DeviceList []DeviceInfo `json:"devices"` 152 | } 153 | devices := &jsonDevices{} 154 | 155 | resp, err := transport.get(fmt.Sprintf(devicePath, "")) 156 | if err != nil { 157 | return devices.DeviceList, err 158 | } 159 | 160 | dec := json.NewDecoder(resp.Body) 161 | err = dec.Decode(&devices) 162 | if err != nil { 163 | return devices.DeviceList, nil 164 | } 165 | 166 | return devices.DeviceList, nil 167 | } 168 | 169 | func unlinkDevice(id int) error { 170 | _, err := transport.del(fmt.Sprintf(devicePath, strconv.Itoa(id))) 171 | if err != nil { 172 | return err 173 | } 174 | return nil 175 | } 176 | 177 | func addNewDevice(ephemeralId, publicKey, verificationCode string) error { 178 | decPk, err := decodeKey(publicKey) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | theirPublicKey := axolotl.NewECPublicKey(decPk) 184 | 185 | pm := &textsecure.ProvisionMessage{ 186 | IdentityKeyPublic: identityKey.PublicKey.Serialize(), 187 | IdentityKeyPrivate: identityKey.PrivateKey.Key()[:], 188 | Number: &config.Tel, 189 | ProvisioningCode: &verificationCode, 190 | } 191 | 192 | ciphertext, err := provisioningCipher(pm, theirPublicKey) 193 | if err != nil { 194 | return err 195 | } 196 | 197 | jsonBody := make(map[string]string) 198 | jsonBody["body"] = base64.StdEncoding.EncodeToString(ciphertext) 199 | body, err := json.Marshal(jsonBody) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | url := fmt.Sprintf(provisioningMessagePath, ephemeralId) 205 | resp, err := transport.putJSON(url, body) 206 | if err != nil { 207 | return err 208 | } 209 | if resp.isError() { 210 | return resp 211 | } 212 | return nil 213 | } 214 | 215 | // PUT /v2/keys/ 216 | func registerPreKeys() error { 217 | body, err := json.MarshalIndent(preKeys, "", "") 218 | if err != nil { 219 | return err 220 | } 221 | 222 | resp, err := transport.putJSON(prekeyMetadataPath, body) 223 | if err != nil { 224 | return err 225 | } 226 | if resp.isError() { 227 | return resp 228 | } 229 | return nil 230 | } 231 | 232 | // GET /v2/keys/{number}/{device_id}?relay={relay} 233 | func getPreKeys(tel string, deviceID string) (*preKeyResponse, error) { 234 | resp, err := transport.get(fmt.Sprintf(prekeyDevicePath, tel, deviceID)) 235 | if err != nil { 236 | return nil, err 237 | } 238 | if resp.isError() { 239 | return nil, resp 240 | } 241 | dec := json.NewDecoder(resp.Body) 242 | k := &preKeyResponse{} 243 | dec.Decode(k) 244 | return k, nil 245 | } 246 | 247 | // jsonContact is the data returned by the server for each registered contact 248 | type jsonContact struct { 249 | Token string `json:"token"` 250 | Relay string `json:"relay"` 251 | } 252 | 253 | // GetRegisteredContacts returns the subset of the local contacts 254 | // that are also registered with the server 255 | func GetRegisteredContacts() ([]Contact, error) { 256 | lc, err := client.GetLocalContacts() 257 | if err != nil { 258 | return nil, fmt.Errorf("could not get local contacts :%s\n", err) 259 | } 260 | tokens := make([]string, len(lc)) 261 | m := make(map[string]Contact) 262 | for i, c := range lc { 263 | t := telToToken(c.Tel) 264 | tokens[i] = t 265 | m[t] = c 266 | } 267 | 268 | contacts := make(map[string][]string) 269 | contacts["contacts"] = tokens 270 | body, err := json.MarshalIndent(contacts, "", " ") 271 | if err != nil { 272 | return nil, err 273 | } 274 | resp, err := transport.putJSON(directoryTokensPath, body) 275 | if err != nil { 276 | return nil, err 277 | } 278 | if resp.isError() { 279 | return nil, resp 280 | } 281 | dec := json.NewDecoder(resp.Body) 282 | var jc map[string][]jsonContact 283 | dec.Decode(&jc) 284 | 285 | lc = make([]Contact, len(jc["contacts"])) 286 | for i, c := range jc["contacts"] { 287 | lc[i] = m[c.Token] 288 | } 289 | return lc, nil 290 | } 291 | 292 | // Attachment handling 293 | 294 | type jsonAllocation struct { 295 | ID uint64 `json:"id"` 296 | Location string `json:"location"` 297 | } 298 | 299 | // GET /v1/attachments/ 300 | func allocateAttachment() (uint64, string, error) { 301 | resp, err := transport.get(allocateAttachmentPath) 302 | if err != nil { 303 | return 0, "", err 304 | } 305 | dec := json.NewDecoder(resp.Body) 306 | var a jsonAllocation 307 | dec.Decode(&a) 308 | return a.ID, a.Location, nil 309 | } 310 | 311 | func getAttachmentLocation(id uint64) (string, error) { 312 | resp, err := transport.get(fmt.Sprintf(attachmentPath, id)) 313 | if err != nil { 314 | return "", err 315 | } 316 | dec := json.NewDecoder(resp.Body) 317 | var a jsonAllocation 318 | dec.Decode(&a) 319 | return a.Location, nil 320 | } 321 | 322 | // Messages 323 | 324 | type jsonMessage struct { 325 | Type int32 `json:"type"` 326 | DestDeviceID uint32 `json:"destinationDeviceId"` 327 | DestRegistrationID uint32 `json:"destinationRegistrationId"` 328 | Body string `json:"body"` 329 | Content string `json:"content"` 330 | Relay string `json:"relay,omitempty"` 331 | } 332 | 333 | func createMessage(msg *outgoingMessage) *textsecure.DataMessage { 334 | dm := &textsecure.DataMessage{} 335 | if msg.msg != "" { 336 | dm.Body = &msg.msg 337 | } 338 | if msg.attachment != nil { 339 | dm.Attachments = []*textsecure.AttachmentPointer{ 340 | { 341 | Id: &msg.attachment.id, 342 | ContentType: &msg.attachment.ct, 343 | Key: msg.attachment.keys, 344 | }, 345 | } 346 | } 347 | if msg.group != nil { 348 | dm.Group = &textsecure.GroupContext{ 349 | Id: msg.group.id, 350 | Type: &msg.group.typ, 351 | Name: &msg.group.name, 352 | Members: msg.group.members, 353 | } 354 | } 355 | 356 | dm.Flags = &msg.flags 357 | 358 | return dm 359 | } 360 | 361 | func padMessage(msg []byte) []byte { 362 | l := (len(msg) + 160) 363 | l = l - l%160 364 | n := make([]byte, l) 365 | copy(n, msg) 366 | n[len(msg)] = 0x80 367 | return n 368 | } 369 | 370 | func stripPadding(msg []byte) []byte { 371 | for i := len(msg) - 1; i >= 0; i-- { 372 | if msg[i] == 0x80 { 373 | return msg[:i] 374 | } 375 | } 376 | return msg 377 | } 378 | 379 | func makePreKeyBundle(tel string, deviceID uint32) (*axolotl.PreKeyBundle, error) { 380 | pkr, err := getPreKeys(tel, strconv.Itoa(int(deviceID))) 381 | if err != nil { 382 | return nil, err 383 | } 384 | 385 | if len(pkr.Devices) != 1 { 386 | return nil, fmt.Errorf("no prekeys for contact %s, device %d\n", tel, deviceID) 387 | } 388 | 389 | d := pkr.Devices[0] 390 | 391 | if d.PreKey == nil { 392 | return nil, fmt.Errorf("no prekey for contact %s, device %d\n", tel, deviceID) 393 | } 394 | 395 | decPK, err := decodeKey(d.PreKey.PublicKey) 396 | if err != nil { 397 | return nil, err 398 | } 399 | 400 | if d.SignedPreKey == nil { 401 | return nil, fmt.Errorf("no signed prekey for contact %s, device %d\n", tel, deviceID) 402 | } 403 | 404 | decSPK, err := decodeKey(d.SignedPreKey.PublicKey) 405 | if err != nil { 406 | return nil, err 407 | } 408 | 409 | decSig, err := decodeSignature(d.SignedPreKey.Signature) 410 | if err != nil { 411 | return nil, err 412 | } 413 | 414 | decIK, err := decodeKey(pkr.IdentityKey) 415 | if err != nil { 416 | return nil, err 417 | } 418 | 419 | pkb, err := axolotl.NewPreKeyBundle( 420 | d.RegistrationID, d.DeviceID, d.PreKey.ID, 421 | axolotl.NewECPublicKey(decPK), int32(d.SignedPreKey.ID), axolotl.NewECPublicKey(decSPK), 422 | decSig, axolotl.NewIdentityKey(decIK)) 423 | if err != nil { 424 | return nil, err 425 | } 426 | 427 | return pkb, nil 428 | } 429 | 430 | func buildMessage(tel string, paddedMessage []byte, devices []uint32, isSync bool) ([]jsonMessage, error) { 431 | recid := recID(tel) 432 | messages := []jsonMessage{} 433 | 434 | for _, devid := range devices { 435 | if !textSecureStore.ContainsSession(recid, devid) { 436 | pkb, err := makePreKeyBundle(tel, devid) 437 | if err != nil { 438 | return nil, err 439 | } 440 | sb := axolotl.NewSessionBuilder(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, pkb.DeviceID) 441 | err = sb.BuildSenderSession(pkb) 442 | if err != nil { 443 | return nil, err 444 | } 445 | } 446 | sc := axolotl.NewSessionCipher(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, devid) 447 | encryptedMessage, messageType, err := sc.SessionEncryptMessage(paddedMessage) 448 | if err != nil { 449 | return nil, err 450 | } 451 | 452 | rrID, err := sc.GetRemoteRegistrationID() 453 | if err != nil { 454 | return nil, err 455 | } 456 | 457 | jmsg := jsonMessage{ 458 | Type: messageType, 459 | DestDeviceID: devid, 460 | DestRegistrationID: rrID, 461 | } 462 | 463 | if isSync { 464 | jmsg.Content = base64.StdEncoding.EncodeToString(encryptedMessage) 465 | } else { 466 | // Use legacy Body for data messages. It seems that iOS devices do 467 | // not support Content yet? 468 | jmsg.Body = base64.StdEncoding.EncodeToString(encryptedMessage) 469 | } 470 | messages = append(messages, jmsg) 471 | } 472 | 473 | return messages, nil 474 | } 475 | 476 | var ( 477 | mismatchedDevicesStatus = 409 478 | staleDevicesStatus = 410 479 | rateLimitExceededStatus = 413 480 | ) 481 | 482 | type jsonMismatchedDevices struct { 483 | MissingDevices []uint32 `json:"missingDevices"` 484 | ExtraDevices []uint32 `json:"extraDevices"` 485 | } 486 | 487 | type jsonStaleDevices struct { 488 | StaleDevices []uint32 `json:"staleDevices"` 489 | } 490 | 491 | type sendMessageResponse struct { 492 | NeedsSync bool `json:"needsSync"` 493 | Timestamp uint64 `json:"-"` 494 | } 495 | 496 | // ErrRemoteGone is returned when the peer reinstalled and lost its session state. 497 | var ErrRemoteGone = errors.New("the remote device is gone (probably reinstalled)") 498 | 499 | var deviceLists = map[string][]uint32{} 500 | 501 | func buildAndSendMessage(tel string, paddedMessage []byte, isSync bool) (*sendMessageResponse, error) { 502 | bm, err := buildMessage(tel, paddedMessage, deviceLists[tel], isSync) 503 | if err != nil { 504 | return nil, err 505 | } 506 | m := make(map[string]interface{}) 507 | m["messages"] = bm 508 | now := uint64(time.Now().UnixNano() / 1000000) 509 | m["timestamp"] = now 510 | m["destination"] = tel 511 | body, err := json.Marshal(m) 512 | if err != nil { 513 | return nil, err 514 | } 515 | resp, err := transport.putJSON(fmt.Sprintf(messagePath, tel), body) 516 | if err != nil { 517 | return nil, err 518 | } 519 | 520 | if resp.Status == mismatchedDevicesStatus { 521 | dec := json.NewDecoder(resp.Body) 522 | var j jsonMismatchedDevices 523 | dec.Decode(&j) 524 | log.Debugf("Mismatched devices: %+v\n", j) 525 | devs := []uint32{} 526 | for _, id := range deviceLists[tel] { 527 | in := true 528 | for _, eid := range j.ExtraDevices { 529 | if id == eid { 530 | in = false 531 | break 532 | } 533 | } 534 | if in { 535 | devs = append(devs, id) 536 | } 537 | } 538 | deviceLists[tel] = append(devs, j.MissingDevices...) 539 | return buildAndSendMessage(tel, paddedMessage, isSync) 540 | } 541 | if resp.Status == staleDevicesStatus { 542 | dec := json.NewDecoder(resp.Body) 543 | var j jsonStaleDevices 544 | dec.Decode(&j) 545 | log.Debugf("Stale devices: %+v\n", j) 546 | for _, id := range j.StaleDevices { 547 | textSecureStore.DeleteSession(recID(tel), id) 548 | } 549 | return buildAndSendMessage(tel, paddedMessage, isSync) 550 | } 551 | if resp.isError() { 552 | return nil, resp 553 | } 554 | 555 | var smRes sendMessageResponse 556 | dec := json.NewDecoder(resp.Body) 557 | dec.Decode(&smRes) 558 | smRes.Timestamp = now 559 | 560 | log.Debugf("SendMessageResponse: %+v\n", smRes) 561 | return &smRes, nil 562 | } 563 | 564 | func sendMessage(msg *outgoingMessage) (uint64, error) { 565 | if _, ok := deviceLists[msg.tel]; !ok { 566 | deviceLists[msg.tel] = []uint32{1} 567 | } 568 | 569 | dm := createMessage(msg) 570 | 571 | b, err := proto.Marshal(dm) 572 | if err != nil { 573 | return 0, err 574 | } 575 | 576 | resp, err := buildAndSendMessage(msg.tel, padMessage(b), false) 577 | if err != nil { 578 | return 0, err 579 | } 580 | 581 | if resp.NeedsSync { 582 | log.Debugf("Needs sync. destination: %s", msg.tel) 583 | sm := &textsecure.SyncMessage{ 584 | Sent: &textsecure.SyncMessage_Sent{ 585 | Destination: &msg.tel, 586 | Timestamp: &resp.Timestamp, 587 | Message: dm, 588 | }, 589 | } 590 | 591 | _, serr := sendSyncMessage(sm) 592 | if serr != nil { 593 | log.WithFields(log.Fields{ 594 | "error": serr, 595 | "destination": msg.tel, 596 | "timestamp": resp.Timestamp, 597 | }).Error("Failed to send sync message") 598 | } 599 | } 600 | 601 | return resp.Timestamp, err 602 | } 603 | 604 | func sendSyncMessage(sm *textsecure.SyncMessage) (uint64, error) { 605 | if _, ok := deviceLists[config.Tel]; !ok { 606 | deviceLists[config.Tel] = []uint32{1} 607 | } 608 | 609 | content := &textsecure.Content{ 610 | SyncMessage: sm, 611 | } 612 | 613 | b, err := proto.Marshal(content) 614 | if err != nil { 615 | return 0, err 616 | } 617 | 618 | resp, err := buildAndSendMessage(config.Tel, padMessage(b), true) 619 | return resp.Timestamp, err 620 | } 621 | -------------------------------------------------------------------------------- /store.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "bytes" 8 | "crypto/sha1" 9 | "errors" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | 18 | "github.com/janimo/textsecure/axolotl" 19 | "golang.org/x/crypto/pbkdf2" 20 | 21 | log "github.com/Sirupsen/logrus" 22 | ) 23 | 24 | // store implements the PreKeyStore, SignedPreKeyStore, 25 | // IdentityStore and SessionStore interfaces from the axolotl package 26 | // Blobs are encrypted with AES-128 and authenticated with HMAC-SHA1 27 | type store struct { 28 | sync.Mutex 29 | preKeysDir string 30 | signedPreKeysDir string 31 | identityDir string 32 | sessionsDir string 33 | 34 | unencrypted bool 35 | aesKey []byte 36 | macKey []byte 37 | } 38 | 39 | func newStore(password, path string) (*store, error) { 40 | ts := &store{ 41 | preKeysDir: filepath.Join(path, "prekeys"), 42 | signedPreKeysDir: filepath.Join(path, "signed_prekeys"), 43 | identityDir: filepath.Join(path, "identity"), 44 | sessionsDir: filepath.Join(path, "sessions"), 45 | unencrypted: password == "", 46 | } 47 | 48 | // Create dirs in case this is first run 49 | if err := os.MkdirAll(ts.preKeysDir, 0700); err != nil { 50 | return nil, err 51 | } 52 | if err := os.MkdirAll(ts.signedPreKeysDir, 0700); err != nil { 53 | return nil, err 54 | } 55 | if err := os.MkdirAll(ts.identityDir, 0700); err != nil { 56 | return nil, err 57 | } 58 | if err := os.MkdirAll(ts.sessionsDir, 0700); err != nil { 59 | return nil, err 60 | } 61 | 62 | // If there is a password, generate the keys from it 63 | if !ts.unencrypted { 64 | salt := make([]byte, 8) 65 | saltFile := filepath.Join(path, "salt") 66 | 67 | var err error 68 | 69 | // Create salt if this is first run 70 | if !exists(saltFile) { 71 | randBytes(salt) 72 | err = ioutil.WriteFile(saltFile, salt, 0600) 73 | if err != nil { 74 | return nil, err 75 | } 76 | } else { 77 | salt, err = ioutil.ReadFile(saltFile) 78 | if err != nil { 79 | return nil, err 80 | } 81 | } 82 | 83 | ts.genKeys(password, salt, 1024) 84 | } 85 | 86 | return ts, nil 87 | } 88 | 89 | // Helpers 90 | 91 | func idToFilename(id uint32) string { 92 | return fmt.Sprintf("%09d", id) 93 | } 94 | 95 | func filenameToID(fname string) (uint32, error) { 96 | var id uint32 97 | _, err := fmt.Sscanf(fname, "%d", &id) 98 | if err != nil { 99 | return 0, err 100 | } 101 | return uint32(id), nil 102 | } 103 | 104 | func (s *store) readNumFromFile(path string) (uint32, error) { 105 | b, err := s.readFile(path) 106 | if err != nil { 107 | return 0, err 108 | } 109 | num, err := strconv.Atoi(string(b)) 110 | if err != nil { 111 | return 0, err 112 | } 113 | return uint32(num), nil 114 | } 115 | 116 | func (s *store) writeNumToFile(path string, num uint32) { 117 | b := []byte(strconv.Itoa(int(num))) 118 | s.writeFile(path, b) 119 | } 120 | 121 | func (s *store) genKeys(password string, salt []byte, count int) { 122 | keys := pbkdf2.Key([]byte(password), salt, count, 16+20, sha1.New) 123 | s.aesKey = keys[:16] 124 | s.macKey = keys[16:] 125 | } 126 | 127 | func (s *store) encrypt(plaintext []byte) ([]byte, error) { 128 | if s.unencrypted { 129 | return plaintext, nil 130 | } 131 | 132 | e, err := aesEncrypt(s.aesKey, plaintext) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | return appendMAC(s.macKey, e), nil 138 | } 139 | 140 | // ErrStoreBadMAC occurs when MAC verification fails on the records stored using password based encryption. 141 | // The probable cause is using a wrong password. 142 | var ErrStoreBadMAC = errors.New("wrong MAC calculated, possibly due to wrong passphrase") 143 | 144 | func (s *store) decrypt(ciphertext []byte) ([]byte, error) { 145 | if s.unencrypted { 146 | return ciphertext, nil 147 | } 148 | 149 | macPos := len(ciphertext) - 32 150 | 151 | if !verifyMAC(s.macKey, ciphertext[:macPos], ciphertext[macPos:]) { 152 | return nil, ErrStoreBadMAC 153 | } 154 | 155 | return aesDecrypt(s.aesKey, ciphertext[:macPos]) 156 | 157 | } 158 | 159 | func (s *store) readFile(path string) ([]byte, error) { 160 | b, err := ioutil.ReadFile(path) 161 | if err != nil { 162 | return nil, err 163 | } 164 | b, err = s.decrypt(b) 165 | return b, err 166 | } 167 | 168 | func (s *store) writeFile(path string, b []byte) error { 169 | b, err := s.encrypt(b) 170 | if err != nil { 171 | return err 172 | } 173 | return ioutil.WriteFile(path, b, 0600) 174 | } 175 | 176 | // Identity store 177 | 178 | func (s *store) GetLocalRegistrationID() (uint32, error) { 179 | regidfile := filepath.Join(s.identityDir, "regid") 180 | return s.readNumFromFile(regidfile) 181 | } 182 | 183 | func (s *store) SetLocalRegistrationID(id uint32) { 184 | regidfile := filepath.Join(s.identityDir, "regid") 185 | s.writeNumToFile(regidfile, id) 186 | } 187 | 188 | func (s *store) GetIdentityKeyPair() (*axolotl.IdentityKeyPair, error) { 189 | idkeyfile := filepath.Join(s.identityDir, "identity_key") 190 | b, err := s.readFile(idkeyfile) 191 | if err != nil { 192 | return nil, err 193 | } 194 | if len(b) != 64 { 195 | return nil, fmt.Errorf("identity key is %d not 64 bytes long", len(b)) 196 | } 197 | return axolotl.NewIdentityKeyPairFromKeys(b[32:], b[:32]), nil 198 | } 199 | 200 | func (s *store) SetIdentityKeyPair(ikp *axolotl.IdentityKeyPair) error { 201 | idkeyfile := filepath.Join(s.identityDir, "identity_key") 202 | b := make([]byte, 64) 203 | copy(b, ikp.PublicKey.Key()[:]) 204 | copy(b[32:], ikp.PrivateKey.Key()[:]) 205 | return s.writeFile(idkeyfile, b) 206 | } 207 | 208 | func (s *store) SaveIdentity(id string, key *axolotl.IdentityKey) error { 209 | idkeyfile := filepath.Join(s.identityDir, "remote_"+id) 210 | return s.writeFile(idkeyfile, key.Key()[:]) 211 | } 212 | 213 | func (s *store) IsTrustedIdentity(id string, key *axolotl.IdentityKey) bool { 214 | if config.AlwaysTrustPeerID { 215 | // Workaround until we handle peer reregistering situations 216 | // more securely and with a better UI. 217 | return true 218 | } 219 | idkeyfile := filepath.Join(s.identityDir, "remote_"+id) 220 | // Trust on first use (TOFU) 221 | if !exists(idkeyfile) { 222 | return true 223 | } 224 | b, err := s.readFile(idkeyfile) 225 | if err != nil { 226 | return false 227 | } 228 | return bytes.Equal(b, key.Key()[:]) 229 | } 230 | 231 | // MyIdentityKey returns our serialized public identity key 232 | func MyIdentityKey() []byte { 233 | return identityKey.PublicKey.Serialize() 234 | } 235 | 236 | // UnknownContactError is returned when an unknown group id is encountered 237 | type UnknownContactError struct { 238 | id string 239 | } 240 | 241 | func (err UnknownContactError) Error() string { 242 | return fmt.Sprintf("unknown contact ID %s", err.id) 243 | } 244 | 245 | // ContactIdentityKey returns the serialized public key of the given contact 246 | func ContactIdentityKey(id string) ([]byte, error) { 247 | s := textSecureStore 248 | idkeyfile := filepath.Join(s.identityDir, "remote_"+recID(id)) 249 | if !exists(idkeyfile) { 250 | return nil, UnknownContactError{id} 251 | } 252 | b, err := s.readFile(idkeyfile) 253 | if err != nil { 254 | return nil, err 255 | } 256 | return append([]byte{5}, b...), nil 257 | } 258 | 259 | // Prekey and signed prekey store 260 | 261 | func (s *store) preKeysFilePath(id uint32) string { 262 | return filepath.Join(s.preKeysDir, idToFilename(id)) 263 | } 264 | 265 | func (s *store) signedPreKeysFilePath(id uint32) string { 266 | return filepath.Join(s.signedPreKeysDir, idToFilename(id)) 267 | } 268 | 269 | func (s *store) LoadPreKey(id uint32) (*axolotl.PreKeyRecord, error) { 270 | b, err := s.readFile(s.preKeysFilePath(id)) 271 | if err != nil { 272 | return nil, err 273 | } 274 | 275 | record, err := axolotl.LoadPreKeyRecord(b) 276 | if err != nil { 277 | return nil, err 278 | } 279 | 280 | return record, nil 281 | } 282 | 283 | func (s *store) LoadSignedPreKey(id uint32) (*axolotl.SignedPreKeyRecord, error) { 284 | b, err := s.readFile(s.signedPreKeysFilePath(id)) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | record, err := axolotl.LoadSignedPreKeyRecord(b) 290 | if err != nil { 291 | return nil, err 292 | } 293 | 294 | return record, nil 295 | } 296 | 297 | func (s *store) LoadSignedPreKeys() []axolotl.SignedPreKeyRecord { 298 | keys := []axolotl.SignedPreKeyRecord{} 299 | //FIXME 300 | return keys 301 | } 302 | 303 | func (s *store) StorePreKey(id uint32, record *axolotl.PreKeyRecord) error { 304 | b, err := record.Serialize() 305 | if err != nil { 306 | return err 307 | } 308 | return s.writeFile(s.preKeysFilePath(id), b) 309 | } 310 | 311 | func (s *store) StoreSignedPreKey(id uint32, record *axolotl.SignedPreKeyRecord) error { 312 | b, err := record.Serialize() 313 | if err != nil { 314 | return err 315 | } 316 | return s.writeFile(s.signedPreKeysFilePath(id), b) 317 | } 318 | 319 | func exists(path string) bool { 320 | _, err := os.Stat(path) 321 | return err == nil 322 | } 323 | 324 | func (s *store) valid() bool { 325 | return s.ContainsPreKey(lastResortPreKeyID) 326 | } 327 | 328 | func (s *store) ContainsPreKey(id uint32) bool { 329 | return exists(s.preKeysFilePath(id)) 330 | } 331 | 332 | func (s *store) ContainsSignedPreKey(id uint32) bool { 333 | return exists(s.signedPreKeysFilePath(id)) 334 | } 335 | 336 | func (s *store) RemovePreKey(id uint32) { 337 | _ = os.Remove(s.preKeysFilePath(id)) 338 | } 339 | 340 | func (s *store) RemoveSignedPreKey(id uint32) { 341 | _ = os.Remove(s.signedPreKeysFilePath(id)) 342 | } 343 | 344 | // HTTP API store 345 | func (s *store) storeHTTPPassword(password string) { 346 | passFile := filepath.Join(s.identityDir, "http_password") 347 | s.writeFile(passFile, []byte(password)) 348 | } 349 | 350 | func (s *store) loadHTTPPassword() (string, error) { 351 | passFile := filepath.Join(s.identityDir, "http_password") 352 | b, err := s.readFile(passFile) 353 | if err != nil { 354 | return "", err 355 | } 356 | return string(b), nil 357 | } 358 | 359 | func (s *store) storeHTTPSignalingKey(key []byte) { 360 | keyFile := filepath.Join(s.identityDir, "http_signaling_key") 361 | s.writeFile(keyFile, key) 362 | } 363 | 364 | func (s *store) loadHTTPSignalingKey() ([]byte, error) { 365 | keyFile := filepath.Join(s.identityDir, "http_signaling_key") 366 | b, err := s.readFile(keyFile) 367 | if err != nil { 368 | return nil, err 369 | } 370 | return b, nil 371 | } 372 | 373 | // Session store 374 | 375 | func (s *store) sessionFilePath(recipientID string, deviceID uint32) string { 376 | return filepath.Join(s.sessionsDir, fmt.Sprintf("%s_%d", recipientID, deviceID)) 377 | } 378 | 379 | func (s *store) GetSubDeviceSessions(recipientID string) []uint32 { 380 | sessions := []uint32{} 381 | 382 | filepath.Walk(s.sessionsDir, func(path string, fi os.FileInfo, err error) error { 383 | if !fi.IsDir() { 384 | i := strings.LastIndex(path, "_") 385 | id, _ := strconv.Atoi(path[i+1:]) 386 | sessions = append(sessions, uint32(id)) 387 | } 388 | return nil 389 | }) 390 | return sessions 391 | } 392 | 393 | func (s *store) LoadSession(recipientID string, deviceID uint32) (*axolotl.SessionRecord, error) { 394 | sfile := s.sessionFilePath(recipientID, deviceID) 395 | b, err := s.readFile(sfile) 396 | if err != nil { 397 | return axolotl.NewSessionRecord(), nil 398 | } 399 | record, err := axolotl.LoadSessionRecord(b) 400 | if err != nil { 401 | return nil, err 402 | } 403 | 404 | return record, nil 405 | } 406 | 407 | func (s *store) StoreSession(recipientID string, deviceID uint32, record *axolotl.SessionRecord) error { 408 | sfile := s.sessionFilePath(recipientID, deviceID) 409 | b, err := record.Serialize() 410 | if err != nil { 411 | return err 412 | } 413 | return s.writeFile(sfile, b) 414 | } 415 | 416 | func (s *store) ContainsSession(recipientID string, deviceID uint32) bool { 417 | sfile := s.sessionFilePath(recipientID, deviceID) 418 | return exists(sfile) 419 | } 420 | 421 | func (s *store) DeleteSession(recipientID string, deviceID uint32) { 422 | sfile := s.sessionFilePath(recipientID, deviceID) 423 | _ = os.Remove(sfile) 424 | } 425 | 426 | func (s *store) DeleteAllSessions(recipientID string) { 427 | log.Debugf("Deleting all sessions for %s\n", recipientID) 428 | sessions := s.GetSubDeviceSessions(recipientID) 429 | for _, dev := range sessions { 430 | _ = os.Remove(s.sessionFilePath(recipientID, dev)) 431 | } 432 | } 433 | 434 | var textSecureStore *store 435 | 436 | func setupStore() error { 437 | var err error 438 | 439 | password := "" 440 | if !config.UnencryptedStorage { 441 | password = config.StoragePassword 442 | if password == "" { 443 | password = client.GetStoragePassword() 444 | } 445 | } 446 | 447 | textSecureStore, err = newStore(password, config.StorageDir) 448 | if err != nil { 449 | return err 450 | } 451 | 452 | if err := setupGroups(); err != nil { 453 | return err 454 | } 455 | 456 | return nil 457 | } 458 | -------------------------------------------------------------------------------- /sync.go: -------------------------------------------------------------------------------- 1 | package textsecure 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | log "github.com/Sirupsen/logrus" 9 | "github.com/golang/protobuf/proto" 10 | "github.com/janimo/textsecure/protobuf" 11 | ) 12 | 13 | // handleSyncMessage handles an incoming SyncMessage. 14 | func handleSyncMessage(src string, timestamp uint64, sm *textsecure.SyncMessage) error { 15 | log.Debugf("SyncMessage recieved at %d", timestamp) 16 | 17 | if sm.GetSent() != nil { 18 | return handleSyncSent(sm.GetSent(), timestamp) 19 | } else if sm.GetRequest() != nil { 20 | return handleSyncRequest(sm.GetRequest()) 21 | } else if sm.GetRead() != nil { 22 | return handleSyncRead(sm.GetRead()) 23 | } else { 24 | log.Errorf("SyncMessage contains no known sync types") 25 | } 26 | 27 | return nil 28 | } 29 | 30 | // handleSyncSent handles sync sent messages 31 | func handleSyncSent(s *textsecure.SyncMessage_Sent, ts uint64) error { 32 | dm := s.GetMessage() 33 | dest := s.GetDestination() 34 | timestamp := s.GetTimestamp() 35 | 36 | if dm == nil { 37 | return fmt.Errorf("DataMessage was nil for SyncMessage_Sent") 38 | } 39 | 40 | flags, err := handleFlags(dest, dm) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | atts, err := handleAttachments(dm) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | gr, err := handleGroups(dest, dm) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | msg := &Message{ 56 | source: dest, 57 | message: dm.GetBody(), 58 | attachments: atts, 59 | group: gr, 60 | timestamp: timestamp, 61 | flags: flags, 62 | } 63 | 64 | if client.SyncSentHandler != nil { 65 | client.SyncSentHandler(msg, ts) 66 | } 67 | 68 | return nil 69 | } 70 | 71 | // handleSyncRequestMessage 72 | func handleSyncRequest(request *textsecure.SyncMessage_Request) error { 73 | if request.GetType() == textsecure.SyncMessage_Request_CONTACTS { 74 | return sendContactUpdate() 75 | } else if request.GetType() == textsecure.SyncMessage_Request_GROUPS { 76 | return sendGroupUpdate() 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // sendContactUpdate 83 | func sendContactUpdate() error { 84 | log.Debugf("Sending contact SyncMessage") 85 | 86 | lc, err := GetRegisteredContacts() 87 | if err != nil { 88 | return fmt.Errorf("could not get local contacts: %s", err) 89 | } 90 | 91 | var buf bytes.Buffer 92 | 93 | for _, c := range lc { 94 | cd := &textsecure.ContactDetails{ 95 | Number: &c.Tel, 96 | Name: &c.Name, 97 | // TODO: handle avatars 98 | } 99 | 100 | b, err := proto.Marshal(cd) 101 | if err != nil { 102 | log.Errorf("Failed to marshal contact details") 103 | continue 104 | } 105 | 106 | buf.Write(varint32(len(b))) 107 | buf.Write(b) 108 | } 109 | 110 | a, err := uploadAttachment(&buf, "application/octet-stream") 111 | if err != nil { 112 | return err 113 | } 114 | 115 | sm := &textsecure.SyncMessage{ 116 | Contacts: &textsecure.SyncMessage_Contacts{ 117 | Blob: &textsecure.AttachmentPointer{ 118 | Id: &a.id, 119 | ContentType: &a.ct, 120 | Key: a.keys, 121 | }, 122 | }, 123 | } 124 | 125 | _, err = sendSyncMessage(sm) 126 | return err 127 | } 128 | 129 | // sendGroupUpdate 130 | func sendGroupUpdate() error { 131 | log.Debugf("Sending group SyncMessage") 132 | 133 | var buf bytes.Buffer 134 | 135 | for _, g := range groups { 136 | gd := &textsecure.GroupDetails{ 137 | Id: g.ID, 138 | Name: &g.Name, 139 | Members: g.Members, 140 | // XXX add support for avatar 141 | // XXX add support for active? 142 | } 143 | 144 | b, err := proto.Marshal(gd) 145 | if err != nil { 146 | log.Errorf("Failed to marshal group details") 147 | continue 148 | } 149 | 150 | buf.Write(varint32(len(b))) 151 | buf.Write(b) 152 | } 153 | 154 | a, err := uploadAttachment(&buf, "application/octet-stream") 155 | if err != nil { 156 | return err 157 | } 158 | 159 | sm := &textsecure.SyncMessage{ 160 | Groups: &textsecure.SyncMessage_Groups{ 161 | Blob: &textsecure.AttachmentPointer{ 162 | Id: &a.id, 163 | ContentType: &a.ct, 164 | Key: a.keys, 165 | }, 166 | }, 167 | } 168 | 169 | _, err = sendSyncMessage(sm) 170 | return err 171 | } 172 | 173 | func handleSyncRead(readMessages []*textsecure.SyncMessage_Read) error { 174 | if client.SyncReadHandler != nil { 175 | for _, s := range readMessages { 176 | client.SyncReadHandler(s.GetSender(), s.GetTimestamp()) 177 | } 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // Encodes a 32bit base 128 variable-length integer and returns the bytes 184 | func varint32(value int) []byte { 185 | buf := make([]byte, binary.MaxVarintLen32) 186 | n := binary.PutUvarint(buf, uint64(value)) 187 | return buf[:n] 188 | } 189 | -------------------------------------------------------------------------------- /textsecure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | // Package textsecure implements the TextSecure client protocol. 5 | package textsecure 6 | 7 | import ( 8 | "encoding/base64" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "os" 13 | "strings" 14 | 15 | "bytes" 16 | 17 | "bitbucket.org/taruti/mimemagic" 18 | 19 | "github.com/golang/protobuf/proto" 20 | 21 | log "github.com/Sirupsen/logrus" 22 | "github.com/janimo/textsecure/axolotl" 23 | "github.com/janimo/textsecure/protobuf" 24 | ) 25 | 26 | // Generate a random 16 byte string used for HTTP Basic Authentication to the server 27 | func generatePassword() string { 28 | b := make([]byte, 16) 29 | randBytes(b[:]) 30 | return base64EncWithoutPadding(b) 31 | } 32 | 33 | // Generate a random 14 bit integer 34 | func generateRegistrationID() uint32 { 35 | return randUint32() & 0x3fff 36 | } 37 | 38 | // Generate a 256 bit AES and a 160 bit HMAC-SHA1 key 39 | // to be used to secure the communication with the server 40 | func generateSignalingKey() []byte { 41 | b := make([]byte, 52) 42 | randBytes(b[:]) 43 | return b 44 | } 45 | 46 | // Base64-encodes without padding the result 47 | func base64EncWithoutPadding(b []byte) string { 48 | s := base64.StdEncoding.EncodeToString(b) 49 | return strings.TrimRight(s, "=") 50 | } 51 | 52 | // Base64-decodes a non-padded string 53 | func base64DecodeNonPadded(s string) ([]byte, error) { 54 | if len(s)%4 != 0 { 55 | s = s + strings.Repeat("=", 4-len(s)%4) 56 | } 57 | return base64.StdEncoding.DecodeString(s) 58 | } 59 | 60 | func encodeKey(key []byte) string { 61 | return base64EncWithoutPadding(append([]byte{5}, key[:]...)) 62 | } 63 | 64 | // ErrBadPublicKey is raised when a given public key is not in the 65 | // expected format. 66 | var ErrBadPublicKey = errors.New("public key not formatted correctly") 67 | 68 | func decodeKey(s string) ([]byte, error) { 69 | b, err := base64DecodeNonPadded(s) 70 | if err != nil { 71 | return nil, err 72 | } 73 | if len(b) != 33 || b[0] != 5 { 74 | return nil, ErrBadPublicKey 75 | } 76 | return b[1:], nil 77 | } 78 | 79 | func decodeSignature(s string) ([]byte, error) { 80 | b, err := base64DecodeNonPadded(s) 81 | if err != nil { 82 | return nil, err 83 | } 84 | if len(b) != 64 { 85 | return nil, fmt.Errorf("signature is %d, not 64 bytes", len(b)) 86 | } 87 | return b, nil 88 | } 89 | 90 | func needsRegistration() bool { 91 | return !textSecureStore.valid() 92 | } 93 | 94 | var identityKey *axolotl.IdentityKeyPair 95 | 96 | type att struct { 97 | id uint64 98 | ct string 99 | keys []byte 100 | } 101 | 102 | type outgoingMessage struct { 103 | tel string 104 | msg string 105 | group *groupMessage 106 | attachment *att 107 | flags uint32 108 | } 109 | 110 | // LinkedDevices returns the list of linked devices 111 | func LinkedDevices() ([]DeviceInfo, error) { 112 | return getLinkedDevices() 113 | } 114 | 115 | // UnlinkDevice removes a linked device 116 | func UnlinkDevice(id int) error { 117 | return unlinkDevice(id) 118 | } 119 | 120 | // NewDeviceVerificationCode returns the verification code for linking devices 121 | func NewDeviceVerificationCode() (string, error) { 122 | return getNewDeviceVerificationCode() 123 | } 124 | 125 | // AddDevice links a new device 126 | func AddDevice(ephemeralId, publicKey, verificationCode string) error { 127 | return addNewDevice(ephemeralId, publicKey, verificationCode) 128 | } 129 | 130 | // SendMessage sends the given text message to the given contact. 131 | func SendMessage(tel, msg string) (uint64, error) { 132 | omsg := &outgoingMessage{ 133 | tel: tel, 134 | msg: msg, 135 | } 136 | return sendMessage(omsg) 137 | } 138 | 139 | func MIMETypeFromReader(r io.Reader) (mime string, reader io.Reader) { 140 | var buf bytes.Buffer 141 | io.CopyN(&buf, r, 1024) 142 | mime = mimemagic.Match("", buf.Bytes()) 143 | return mime, io.MultiReader(&buf, r) 144 | } 145 | 146 | // SendAttachment sends the contents of a reader, along 147 | // with an optional message to a given contact. 148 | func SendAttachment(tel, msg string, r io.Reader) (uint64, error) { 149 | ct, r := MIMETypeFromReader(r) 150 | a, err := uploadAttachment(r, ct) 151 | if err != nil { 152 | return 0, err 153 | } 154 | omsg := &outgoingMessage{ 155 | tel: tel, 156 | msg: msg, 157 | attachment: a, 158 | } 159 | return sendMessage(omsg) 160 | } 161 | 162 | // EndSession terminates the session with the given peer. 163 | func EndSession(tel string, msg string) (uint64, error) { 164 | omsg := &outgoingMessage{ 165 | tel: tel, 166 | msg: msg, 167 | flags: uint32(textsecure.DataMessage_END_SESSION), 168 | } 169 | ts, err := sendMessage(omsg) 170 | if err != nil { 171 | return 0, err 172 | } 173 | textSecureStore.DeleteAllSessions(recID(tel)) 174 | return ts, nil 175 | } 176 | 177 | // Attachment represents an attachment received from a peer 178 | type Attachment struct { 179 | R io.Reader 180 | MimeType string 181 | } 182 | 183 | // Message represents a message received from the peer. 184 | // It can optionally include attachments and be sent to a group. 185 | type Message struct { 186 | source string 187 | message string 188 | attachments []*Attachment 189 | group *Group 190 | timestamp uint64 191 | flags uint32 192 | } 193 | 194 | // Source returns the ID of the sender of the message. 195 | func (m *Message) Source() string { 196 | return m.source 197 | } 198 | 199 | // Message returns the message body. 200 | func (m *Message) Message() string { 201 | return m.message 202 | } 203 | 204 | // Attachments returns the list of attachments on the message. 205 | func (m *Message) Attachments() []*Attachment { 206 | return m.attachments 207 | } 208 | 209 | // Group returns group information. 210 | func (m *Message) Group() *Group { 211 | return m.group 212 | } 213 | 214 | // Timestamp returns the timestamp of the message 215 | func (m *Message) Timestamp() uint64 { 216 | return m.timestamp 217 | } 218 | 219 | // Flags returns the flags in the message 220 | func (m *Message) Flags() uint32 { 221 | return m.flags 222 | } 223 | 224 | // Client contains application specific data and callbacks. 225 | type Client struct { 226 | GetPhoneNumber func() string 227 | GetVerificationCode func() string 228 | GetStoragePassword func() string 229 | GetConfig func() (*Config, error) 230 | GetLocalContacts func() ([]Contact, error) 231 | MessageHandler func(*Message) 232 | ReceiptHandler func(string, uint32, uint64) 233 | SyncReadHandler func(string, uint64) 234 | SyncSentHandler func(*Message, uint64) 235 | RegistrationDone func() 236 | } 237 | 238 | var ( 239 | config *Config 240 | client *Client 241 | ) 242 | 243 | // setupLogging sets the logging verbosity level based on configuration 244 | // and environment variables 245 | func setupLogging() { 246 | loglevel := config.LogLevel 247 | if loglevel == "" { 248 | loglevel = os.Getenv("TEXTSECURE_LOGLEVEL") 249 | } 250 | 251 | switch strings.ToUpper(loglevel) { 252 | case "DEBUG": 253 | log.SetLevel(log.DebugLevel) 254 | case "INFO": 255 | log.SetLevel(log.InfoLevel) 256 | case "WARN": 257 | log.SetLevel(log.WarnLevel) 258 | case "ERROR": 259 | log.SetLevel(log.ErrorLevel) 260 | default: 261 | log.SetLevel(log.ErrorLevel) 262 | } 263 | 264 | log.SetFormatter(&log.TextFormatter{ 265 | FullTimestamp: true, 266 | TimestampFormat: "2006/01/02 15:04:05", 267 | }) 268 | } 269 | 270 | // Setup initializes the package. 271 | func Setup(c *Client) error { 272 | var err error 273 | 274 | client = c 275 | 276 | config, err = loadConfig() 277 | if err != nil { 278 | return err 279 | } 280 | 281 | setupLogging() 282 | err = setupStore() 283 | if err != nil { 284 | return err 285 | } 286 | 287 | if needsRegistration() { 288 | registrationInfo.registrationID = generateRegistrationID() 289 | textSecureStore.SetLocalRegistrationID(registrationInfo.registrationID) 290 | 291 | registrationInfo.password = generatePassword() 292 | textSecureStore.storeHTTPPassword(registrationInfo.password) 293 | 294 | registrationInfo.signalingKey = generateSignalingKey() 295 | textSecureStore.storeHTTPSignalingKey(registrationInfo.signalingKey) 296 | 297 | identityKey = axolotl.GenerateIdentityKeyPair() 298 | err := textSecureStore.SetIdentityKeyPair(identityKey) 299 | if err != nil { 300 | return err 301 | } 302 | 303 | err = registerDevice() 304 | if err != nil { 305 | return err 306 | } 307 | } 308 | registrationInfo.registrationID, err = textSecureStore.GetLocalRegistrationID() 309 | if err != nil { 310 | return err 311 | } 312 | registrationInfo.password, err = textSecureStore.loadHTTPPassword() 313 | if err != nil { 314 | return err 315 | } 316 | registrationInfo.signalingKey, err = textSecureStore.loadHTTPSignalingKey() 317 | if err != nil { 318 | return err 319 | } 320 | setupTransporter() 321 | identityKey, err = textSecureStore.GetIdentityKeyPair() 322 | return err 323 | } 324 | 325 | func registerDevice() error { 326 | if config.Tel == "" { 327 | config.Tel = client.GetPhoneNumber() 328 | } 329 | setupTransporter() 330 | code, err := requestCode(config.Tel, config.VerificationType) 331 | if err != nil { 332 | return err 333 | } 334 | if config.VerificationType != "dev" { 335 | code = client.GetVerificationCode() 336 | } 337 | err = verifyCode(code) 338 | if err != nil { 339 | return err 340 | } 341 | err = generatePreKeys() 342 | if err != nil { 343 | return err 344 | } 345 | err = generatePreKeyState() 346 | if err != nil { 347 | return err 348 | } 349 | err = registerPreKeys() 350 | if err != nil { 351 | return err 352 | } 353 | if client.RegistrationDone != nil { 354 | client.RegistrationDone() 355 | } 356 | return nil 357 | } 358 | 359 | func handleReceipt(env *textsecure.Envelope) { 360 | if client.ReceiptHandler != nil { 361 | client.ReceiptHandler(env.GetSource(), env.GetSourceDevice(), env.GetTimestamp()) 362 | } 363 | } 364 | 365 | func recID(source string) string { 366 | return source[1:] 367 | } 368 | 369 | func handleMessage(src string, timestamp uint64, b []byte, legacy bool) error { 370 | b = stripPadding(b) 371 | 372 | if legacy { 373 | dm := &textsecure.DataMessage{} 374 | err := proto.Unmarshal(b, dm) 375 | if err != nil { 376 | return err 377 | } 378 | return handleDataMessage(src, timestamp, dm) 379 | } 380 | 381 | content := &textsecure.Content{} 382 | err := proto.Unmarshal(b, content) 383 | if err != nil { 384 | return err 385 | } 386 | if dm := content.GetDataMessage(); dm != nil { 387 | return handleDataMessage(src, timestamp, dm) 388 | } else if sm := content.GetSyncMessage(); sm != nil && config.Tel == src { 389 | return handleSyncMessage(src, timestamp, sm) 390 | } 391 | 392 | log.Errorf("Unknown message content received") 393 | return nil 394 | } 395 | 396 | // EndSessionFlag signals that this message resets the session 397 | var EndSessionFlag uint32 = 1 398 | 399 | func handleFlags(src string, dm *textsecure.DataMessage) (uint32, error) { 400 | flags := uint32(0) 401 | if dm.GetFlags() == uint32(textsecure.DataMessage_END_SESSION) { 402 | flags = EndSessionFlag 403 | textSecureStore.DeleteAllSessions(recID(src)) 404 | } 405 | return flags, nil 406 | } 407 | 408 | // handleDataMessage handles an incoming DataMessage and calls client callbacks 409 | func handleDataMessage(src string, timestamp uint64, dm *textsecure.DataMessage) error { 410 | flags, err := handleFlags(src, dm) 411 | if err != nil { 412 | return err 413 | } 414 | 415 | atts, err := handleAttachments(dm) 416 | if err != nil { 417 | return err 418 | } 419 | 420 | gr, err := handleGroups(src, dm) 421 | if err != nil { 422 | return err 423 | } 424 | 425 | msg := &Message{ 426 | source: src, 427 | message: dm.GetBody(), 428 | attachments: atts, 429 | group: gr, 430 | timestamp: timestamp, 431 | flags: flags, 432 | } 433 | 434 | if client.MessageHandler != nil { 435 | client.MessageHandler(msg) 436 | } 437 | return nil 438 | } 439 | 440 | func getMessage(env *textsecure.Envelope) ([]byte, bool) { 441 | if msg := env.GetContent(); msg != nil { 442 | return msg, false 443 | } 444 | return env.GetLegacyMessage(), true 445 | } 446 | 447 | // MessageTypeNotImplementedError is raised in the unlikely event that an unhandled protocol message type is received. 448 | type MessageTypeNotImplementedError struct { 449 | typ uint32 450 | } 451 | 452 | func (err MessageTypeNotImplementedError) Error() string { 453 | return fmt.Sprintf("not implemented message type %d", err.typ) 454 | } 455 | 456 | // ErrInvalidMACForMessage signals an incoming message with invalid MAC. 457 | var ErrInvalidMACForMessage = errors.New("invalid MAC for incoming message") 458 | 459 | // Authenticate and decrypt a received message 460 | func handleReceivedMessage(msg []byte) error { 461 | // ignore empty messages 462 | if len(msg) == 0 { 463 | return nil 464 | } 465 | 466 | macpos := len(msg) - 10 467 | tmac := msg[macpos:] 468 | aesKey := registrationInfo.signalingKey[:32] 469 | macKey := registrationInfo.signalingKey[32:] 470 | if !axolotl.ValidTruncMAC(msg[:macpos], tmac, macKey) { 471 | return ErrInvalidMACForMessage 472 | } 473 | ciphertext := msg[1:macpos] 474 | 475 | plaintext, err := axolotl.Decrypt(aesKey, ciphertext) 476 | if err != nil { 477 | return err 478 | } 479 | env := &textsecure.Envelope{} 480 | err = proto.Unmarshal(plaintext, env) 481 | if err != nil { 482 | return err 483 | } 484 | 485 | recid := recID(env.GetSource()) 486 | sc := axolotl.NewSessionCipher(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, env.GetSourceDevice()) 487 | switch *env.Type { 488 | case textsecure.Envelope_RECEIPT: 489 | handleReceipt(env) 490 | return nil 491 | case textsecure.Envelope_CIPHERTEXT: 492 | msg, legacy := getMessage(env) 493 | wm, err := axolotl.LoadWhisperMessage(msg) 494 | if err != nil { 495 | return err 496 | } 497 | b, err := sc.SessionDecryptWhisperMessage(wm) 498 | if _, ok := err.(axolotl.DuplicateMessageError); ok { 499 | log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) 500 | return nil 501 | } 502 | if _, ok := err.(axolotl.InvalidMessageError); ok { 503 | log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) 504 | return nil 505 | } 506 | if err != nil { 507 | return err 508 | } 509 | err = handleMessage(env.GetSource(), env.GetTimestamp(), b, legacy) 510 | if err != nil { 511 | return err 512 | } 513 | 514 | case textsecure.Envelope_PREKEY_BUNDLE: 515 | msg, legacy := getMessage(env) 516 | pkwm, err := axolotl.LoadPreKeyWhisperMessage(msg) 517 | if err != nil { 518 | return err 519 | } 520 | b, err := sc.SessionDecryptPreKeyWhisperMessage(pkwm) 521 | if _, ok := err.(axolotl.DuplicateMessageError); ok { 522 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 523 | return nil 524 | } 525 | if _, ok := err.(axolotl.PreKeyNotFoundError); ok { 526 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 527 | return nil 528 | } 529 | if _, ok := err.(axolotl.InvalidMessageError); ok { 530 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 531 | return nil 532 | } 533 | if err != nil { 534 | return err 535 | } 536 | err = handleMessage(env.GetSource(), env.GetTimestamp(), b, legacy) 537 | if err != nil { 538 | return err 539 | } 540 | default: 541 | return MessageTypeNotImplementedError{uint32(*env.Type)} 542 | } 543 | 544 | return nil 545 | } 546 | -------------------------------------------------------------------------------- /transport.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "bytes" 8 | "crypto/tls" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/url" 13 | 14 | log "github.com/Sirupsen/logrus" 15 | ) 16 | 17 | var transport transporter 18 | 19 | func setupTransporter() { 20 | setupCA() 21 | transport = newHTTPTransporter(config.Server, config.Tel, registrationInfo.password) 22 | } 23 | 24 | type response struct { 25 | Status int 26 | Body io.ReadCloser 27 | } 28 | 29 | func (r *response) isError() bool { 30 | return r.Status < 200 || r.Status >= 300 31 | } 32 | 33 | func (r *response) Error() string { 34 | return fmt.Sprintf("status code %d\n", r.Status) 35 | } 36 | 37 | type transporter interface { 38 | get(url string) (*response, error) 39 | del(url string) (*response, error) 40 | putJSON(url string, body []byte) (*response, error) 41 | putBinary(url string, body []byte) (*response, error) 42 | } 43 | 44 | type httpTransporter struct { 45 | baseURL string 46 | user string 47 | pass string 48 | client *http.Client 49 | } 50 | 51 | func getProxy(req *http.Request) (*url.URL, error) { 52 | if config.ProxyServer != "" { 53 | u, err := url.Parse(config.ProxyServer) 54 | if err == nil { 55 | return u, nil 56 | } 57 | } 58 | return http.ProxyFromEnvironment(req) 59 | } 60 | 61 | func newHTTPTransporter(baseURL, user, pass string) *httpTransporter { 62 | client := &http.Client{ 63 | Transport: &http.Transport{ 64 | TLSClientConfig: &tls.Config{RootCAs: rootCA}, 65 | Proxy: getProxy, 66 | }, 67 | } 68 | 69 | return &httpTransporter{baseURL, user, pass, client} 70 | } 71 | 72 | func (ht *httpTransporter) get(url string) (*response, error) { 73 | req, err := http.NewRequest("GET", ht.baseURL+url, nil) 74 | if err != nil { 75 | return nil, err 76 | } 77 | if config.UserAgent != "" { 78 | req.Header.Set("User-Agent", config.UserAgent) 79 | } 80 | req.SetBasicAuth(ht.user, ht.pass) 81 | resp, err := ht.client.Do(req) 82 | if err != nil { 83 | return nil, err 84 | } 85 | r := &response{} 86 | if resp != nil { 87 | r.Status = resp.StatusCode 88 | r.Body = resp.Body 89 | } 90 | 91 | log.Debugf("GET %s %d\n", url, r.Status) 92 | 93 | return r, err 94 | } 95 | 96 | func (ht *httpTransporter) del(url string) (*response, error) { 97 | req, err := http.NewRequest("DELETE", ht.baseURL+url, nil) 98 | if err != nil { 99 | return nil, err 100 | } 101 | if config.UserAgent != "" { 102 | req.Header.Set("User-Agent", config.UserAgent) 103 | } 104 | req.SetBasicAuth(ht.user, ht.pass) 105 | resp, err := ht.client.Do(req) 106 | if err != nil { 107 | return nil, err 108 | } 109 | r := &response{} 110 | if resp != nil { 111 | r.Status = resp.StatusCode 112 | r.Body = resp.Body 113 | } 114 | 115 | log.Debugf("DELETE %s %d\n", url, r.Status) 116 | 117 | return r, err 118 | } 119 | 120 | func (ht *httpTransporter) put(url string, body []byte, ct string) (*response, error) { 121 | br := bytes.NewReader(body) 122 | req, err := http.NewRequest("PUT", ht.baseURL+url, br) 123 | if err != nil { 124 | return nil, err 125 | } 126 | if config.UserAgent != "" { 127 | req.Header.Set("User-Agent", config.UserAgent) 128 | } 129 | req.Header.Add("Content-type", ct) 130 | req.SetBasicAuth(ht.user, ht.pass) 131 | resp, err := ht.client.Do(req) 132 | if err != nil { 133 | return nil, err 134 | } 135 | r := &response{} 136 | if resp != nil { 137 | r.Status = resp.StatusCode 138 | r.Body = resp.Body 139 | } 140 | 141 | log.Debugf("PUT %s %d\n", url, r.Status) 142 | 143 | return r, err 144 | } 145 | 146 | func (ht *httpTransporter) putJSON(url string, body []byte) (*response, error) { 147 | return ht.put(url, body, "application/json") 148 | } 149 | 150 | func (ht *httpTransporter) putBinary(url string, body []byte) (*response, error) { 151 | return ht.put(url, body, "application/octet-stream") 152 | } 153 | -------------------------------------------------------------------------------- /websocket.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Canonical Ltd. 2 | // Licensed under the GPLv3, see the COPYING file for details. 3 | 4 | package textsecure 5 | 6 | import ( 7 | "errors" 8 | "net" 9 | "net/http" 10 | "net/http/httputil" 11 | "net/url" 12 | "strings" 13 | "time" 14 | 15 | "github.com/golang/protobuf/proto" 16 | "github.com/janimo/textsecure/protobuf" 17 | "golang.org/x/net/websocket" 18 | 19 | "crypto/tls" 20 | 21 | log "github.com/Sirupsen/logrus" 22 | ) 23 | 24 | type wsConn struct { 25 | conn *websocket.Conn 26 | id uint64 27 | closing bool 28 | } 29 | 30 | var wsconn *wsConn 31 | 32 | // set up a tunnel via HTTP CONNECT 33 | // see https://gist.github.com/madmo/8548738 34 | func httpConnect(proxy string, wsConfig *websocket.Config) (net.Conn, error) { 35 | p, err := net.Dial("tcp", proxy) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | req := http.Request{ 41 | Method: "CONNECT", 42 | URL: &url.URL{}, 43 | Host: wsConfig.Location.Host, 44 | } 45 | 46 | cc := httputil.NewClientConn(p, nil) 47 | cc.Do(&req) 48 | if err != nil && err != httputil.ErrPersistEOF { 49 | return nil, err 50 | } 51 | 52 | conn, _ := cc.Hijack() 53 | return conn, nil 54 | } 55 | 56 | func newWSConn(originURL, user, pass string) (*wsConn, error) { 57 | v := url.Values{} 58 | v.Set("login", user) 59 | v.Set("password", pass) 60 | params := v.Encode() 61 | wsURL := strings.Replace(originURL, "http", "ws", 1) + "?" + params 62 | 63 | wsConfig, err := websocket.NewConfig(wsURL, originURL) 64 | if err != nil { 65 | return nil, err 66 | } 67 | wsConfig.TlsConfig = &tls.Config{RootCAs: rootCA} 68 | 69 | var wsc *websocket.Conn 70 | 71 | req := http.Request{ 72 | URL: &url.URL{}, 73 | } 74 | 75 | proxyURL, err := getProxy(&req) 76 | if err != nil { 77 | return nil, err 78 | } 79 | if proxyURL == nil { 80 | wsc, err = websocket.DialConfig(wsConfig) 81 | if err != nil { 82 | return nil, err 83 | } 84 | } else { 85 | conn, err := httpConnect(proxyURL.Host, wsConfig) 86 | if err != nil { 87 | return nil, err 88 | } 89 | if wsConfig.Location.Scheme == "wss" { 90 | conn = tls.Client(conn, wsConfig.TlsConfig) 91 | } 92 | 93 | wsc, err = websocket.NewClient(wsConfig, conn) 94 | if err != nil { 95 | return nil, err 96 | } 97 | } 98 | return &wsConn{conn: wsc}, nil 99 | } 100 | 101 | func (wsc *wsConn) send(b []byte) error { 102 | return websocket.Message.Send(wsc.conn, b) 103 | } 104 | 105 | func (wsc *wsConn) receive() ([]byte, error) { 106 | var b []byte 107 | err := websocket.Message.Receive(wsc.conn, &b) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | return b, nil 113 | } 114 | 115 | func (wsc *wsConn) close() error { 116 | return wsc.conn.Close() 117 | } 118 | 119 | func (wsc *wsConn) sendRequest(verb, path string, body []byte, id *uint64) error { 120 | typ := textsecure.WebSocketMessage_REQUEST 121 | 122 | wsm := &textsecure.WebSocketMessage{ 123 | Type: &typ, 124 | Request: &textsecure.WebSocketRequestMessage{ 125 | Verb: &verb, 126 | Path: &path, 127 | Body: body, 128 | Id: id, 129 | }, 130 | } 131 | 132 | b, err := proto.Marshal(wsm) 133 | if err != nil { 134 | return err 135 | } 136 | return wsc.send(b) 137 | } 138 | 139 | func (wsc *wsConn) keepAlive() { 140 | for { 141 | if wsc.closing { 142 | return 143 | } 144 | err := wsc.sendRequest("GET", "/v1/keepalive", nil, nil) 145 | if err != nil { 146 | log.Error(err) 147 | return 148 | } 149 | time.Sleep(time.Second * 15) 150 | } 151 | } 152 | 153 | func (wsc *wsConn) sendAck(id uint64) error { 154 | typ := textsecure.WebSocketMessage_RESPONSE 155 | message := "OK" 156 | status := uint32(200) 157 | 158 | wsm := &textsecure.WebSocketMessage{ 159 | Type: &typ, 160 | Response: &textsecure.WebSocketResponseMessage{ 161 | Id: &id, 162 | Status: &status, 163 | Message: &message, 164 | }, 165 | } 166 | 167 | b, err := proto.Marshal(wsm) 168 | if err != nil { 169 | return err 170 | } 171 | return wsc.send(b) 172 | } 173 | 174 | // StartListening connects to the server and handles incoming websocket messages. 175 | func StartListening() error { 176 | var err error 177 | wsconn, err = newWSConn(config.Server+"/v1/websocket/", config.Tel, registrationInfo.password) 178 | if err != nil { 179 | return err 180 | } 181 | 182 | go wsconn.keepAlive() 183 | 184 | for { 185 | bmsg, err := wsconn.receive() 186 | if err != nil { 187 | log.Error(err) 188 | break 189 | } 190 | 191 | //do not handle and ack the message if closing 192 | 193 | if wsconn.closing { 194 | break 195 | } 196 | 197 | wsm := &textsecure.WebSocketMessage{} 198 | err = proto.Unmarshal(bmsg, wsm) 199 | if err != nil { 200 | log.Error(err) 201 | continue 202 | } 203 | m := wsm.GetRequest().GetBody() 204 | err = handleReceivedMessage(m) 205 | if err != nil { 206 | log.Error(err) 207 | continue 208 | } 209 | err = wsconn.sendAck(wsm.GetRequest().GetId()) 210 | if err != nil { 211 | log.Error(err) 212 | break 213 | } 214 | } 215 | wsconn.close() 216 | return err 217 | } 218 | 219 | // ErrNotListening is returned when trying to stop listening when there's no 220 | // valid listening connection set up 221 | var ErrNotListening = errors.New("there is no listening connection to stop") 222 | 223 | // StopListening disables the receiving of messages. 224 | func StopListening() error { 225 | if wsconn == nil { 226 | return ErrNotListening 227 | } 228 | wsconn.closing = true 229 | return nil 230 | } 231 | --------------------------------------------------------------------------------