├── .gitignore ├── 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 │ ├── FingerprintProtocol.pb.go │ ├── FingerprintProtocol.proto │ ├── LocalStorageProtocol.pb.go │ ├── LocalStorageProtocol.proto │ ├── Makefile │ ├── 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 ├── doc.md ├── groups.go ├── link.go ├── prekeys.go ├── protobuf ├── Makefile ├── Provisioning.pb.go ├── Provisioning.proto ├── SignalService.pb.go ├── SignalService.proto ├── StickerResources.pb.go ├── StickerResources.proto ├── WebSocketResources.pb.go └── WebSocketResources.proto ├── rootca.go ├── server.go ├── store.go ├── sync.go ├── textsecure.go ├── transport.go └── websocket.go /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/textsecure/textsecure 2 | -------------------------------------------------------------------------------- /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/nanu-c/textsecure-qml 8 | 9 | Automatically generated documentation can be found on [GoDoc](https://godoc.org/github.com/nanu-c/textsecure) 10 | 11 | Installation 12 | ------------ 13 | 14 | This command will install both the library and the test client. 15 | 16 | go get github.com/nanu-c/textsecure/cmd/textsecure 17 | 18 | 19 | Configuration 20 | ------------- 21 | 22 | Copy cmd/textsecure/.config to a directory and modify it, then run the tool from that directory. 23 | 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. 24 | 25 | Usage 26 | ----- 27 | 28 | **Do not run multiple instances of the app from the same directory, it (and the server) can get confused** 29 | 30 | This will show the supported command line flags 31 | 32 | textsecure -h 33 | 34 | 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. 35 | 36 | Discussions 37 | ----------- 38 | 39 | User and developer discussions happen on the [mailing list](https://groups.google.com/forum/#!forum/textsecure-go) 40 | -------------------------------------------------------------------------------- /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 | "crypto/sha256" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "net/http" 14 | "strconv" 15 | 16 | textsecure "github.com/nanu-c/textsecure/protobuf" 17 | ) 18 | 19 | // getAttachment downloads an encrypted attachment blob from the given URL 20 | func getAttachment(url string) (io.ReadCloser, error) { 21 | req, err := http.NewRequest("GET", url, nil) 22 | req.Header.Add("Content-Type", "application/octet-stream") 23 | 24 | client := newHTTPClient() 25 | resp, err := client.Do(req) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return resp.Body, nil 31 | } 32 | 33 | // putAttachment uploads an encrypted attachment to the given URL 34 | func putAttachment(url string, body []byte) ([]byte, error) { 35 | br := bytes.NewReader(body) 36 | req, err := http.NewRequest("PUT", url, br) 37 | if err != nil { 38 | return nil, err 39 | } 40 | req.Header.Add("Content-Type", "application/octet-stream") 41 | req.Header.Add("Content-Length", strconv.Itoa(len(body))) 42 | 43 | client := newHTTPClient() 44 | resp, err := client.Do(req) 45 | if err != nil { 46 | return nil, err 47 | } 48 | defer resp.Body.Close() 49 | 50 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 51 | return nil, fmt.Errorf("HTTP status %d\n", resp.StatusCode) 52 | } 53 | 54 | hasher := sha256.New() 55 | hasher.Write(body) 56 | 57 | return hasher.Sum(nil), nil 58 | } 59 | 60 | // uploadAttachment encrypts, authenticates and uploads a given attachment to a location requested from the server 61 | func uploadAttachment(r io.Reader, ct string) (*att, error) { 62 | //combined AES-256 and HMAC-SHA256 key 63 | keys := make([]byte, 64) 64 | randBytes(keys) 65 | 66 | b, err := ioutil.ReadAll(r) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | plaintextLength := len(b) 72 | 73 | e, err := aesEncrypt(keys[:32], b) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | m := appendMAC(keys[32:], e) 79 | 80 | id, location, err := allocateAttachment() 81 | if err != nil { 82 | return nil, err 83 | } 84 | digest, err := putAttachment(location, m) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | return &att{id, ct, keys, digest, uint32(plaintextLength)}, nil 90 | } 91 | 92 | // ErrInvalidMACForAttachment signals that the downloaded attachment has an invalid MAC. 93 | var ErrInvalidMACForAttachment = errors.New("invalid MAC for attachment") 94 | 95 | func handleSingleAttachment(a *textsecure.AttachmentPointer) (*Attachment, error) { 96 | loc, err := getAttachmentLocation(*a.Id) 97 | if err != nil { 98 | return nil, err 99 | } 100 | r, err := getAttachment(loc) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer r.Close() 105 | 106 | b, err := ioutil.ReadAll(r) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | l := len(b) - 32 112 | if !verifyMAC(a.Key[32:], b[:l], b[l:]) { 113 | return nil, ErrInvalidMACForAttachment 114 | } 115 | 116 | b, err = aesDecrypt(a.Key[:32], b[:l]) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | // TODO: verify digest 122 | 123 | return &Attachment{bytes.NewReader(b), a.GetContentType()}, nil 124 | } 125 | 126 | func handleAttachments(dm *textsecure.DataMessage) ([]*Attachment, error) { 127 | atts := dm.GetAttachments() 128 | if atts == nil { 129 | return nil, nil 130 | } 131 | 132 | all := make([]*Attachment, len(atts)) 133 | var err error 134 | for i, a := range atts { 135 | all[i], err = handleSingleAttachment(a) 136 | if err != nil { 137 | return nil, err 138 | } 139 | } 140 | return all, nil 141 | } 142 | -------------------------------------------------------------------------------- /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/nanu-c/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 | if len(serialized) == 0 || len(serialized)-macLength < 1 { 43 | return nil, ErrIncompleteWhisperMessage 44 | } 45 | 46 | version := highBitsToInt(serialized[0]) 47 | message := serialized[1 : len(serialized)-macLength] 48 | 49 | if version != currentVersion { 50 | return nil, UnsupportedVersionError{version} 51 | } 52 | pwm := &protobuf.SignalMessage{} 53 | err := proto.Unmarshal(message, pwm) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if pwm.GetCiphertext() == nil || pwm.GetRatchetKey() == nil { 59 | return nil, ErrIncompleteWhisperMessage 60 | } 61 | 62 | wm := &WhisperMessage{ 63 | Version: version, 64 | Counter: pwm.GetCounter(), 65 | PreviousCounter: pwm.GetPreviousCounter(), 66 | RatchetKey: NewECPublicKey(pwm.GetRatchetKey()[1:]), 67 | Ciphertext: pwm.GetCiphertext(), 68 | serialized: serialized, 69 | } 70 | 71 | return wm, nil 72 | } 73 | 74 | func newWhisperMessage(messageVersion byte, macKey []byte, ratchetKey *ECPublicKey, 75 | counter, previousCounter uint32, ciphertext []byte, 76 | senderIdentity, receiverIdentity *IdentityKey) (*WhisperMessage, error) { 77 | 78 | version := makeVersionByte(messageVersion, currentVersion) 79 | 80 | pwm := &protobuf.SignalMessage{ 81 | RatchetKey: ratchetKey.Serialize(), 82 | Counter: &counter, 83 | PreviousCounter: &previousCounter, 84 | Ciphertext: ciphertext, 85 | } 86 | 87 | message, err := proto.Marshal(pwm) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | data := append([]byte{version}, message...) 93 | mac := getMac(messageVersion, senderIdentity, receiverIdentity, macKey, data) 94 | 95 | wm := &WhisperMessage{ 96 | Version: messageVersion, 97 | Counter: counter, 98 | PreviousCounter: previousCounter, 99 | RatchetKey: ratchetKey, 100 | 101 | Ciphertext: ciphertext, 102 | serialized: append(data, mac...), 103 | } 104 | 105 | return wm, nil 106 | 107 | } 108 | 109 | func getMac(version byte, senderIdentity, receiverIdentity *IdentityKey, macKey, serialized []byte) []byte { 110 | msg := []byte{} 111 | if version >= 3 { 112 | msg = append(msg, senderIdentity.Serialize()...) 113 | msg = append(msg, receiverIdentity.Serialize()...) 114 | } 115 | msg = append(msg, serialized...) 116 | return ComputeTruncatedMAC(msg, macKey, macLength) 117 | } 118 | 119 | func (wm *WhisperMessage) verifyMAC(senderIdentity, receiverIdentity *IdentityKey, macKey []byte) bool { 120 | macpos := len(wm.serialized) - macLength 121 | 122 | ourMAC := getMac(wm.Version, senderIdentity, receiverIdentity, macKey, wm.serialized[:macpos]) 123 | theirMAC := wm.serialized[macpos:] 124 | return hmac.Equal(ourMAC, theirMAC) 125 | } 126 | 127 | func (wm *WhisperMessage) serialize() []byte { 128 | return wm.serialized 129 | } 130 | 131 | // PreKeyWhisperMessage represents a WhisperMessage and additional prekey 132 | // metadata used for the initial handshake in a conversation. 133 | type PreKeyWhisperMessage struct { 134 | Version byte 135 | RegistrationID uint32 136 | PreKeyID uint32 137 | SignedPreKeyID uint32 138 | BaseKey *ECPublicKey 139 | IdentityKey *IdentityKey 140 | Message *WhisperMessage 141 | serialized []byte 142 | } 143 | 144 | // ErrIncompletePreKeyWhisperMessage is returned when an incomplete PreKeyWhisperMessage is received. 145 | var ErrIncompletePreKeyWhisperMessage = errors.New("incomplete PreKeyWhisperMessage") 146 | 147 | // LoadPreKeyWhisperMessage creates a PreKeyWhisperMessage from serialized bytes. 148 | func LoadPreKeyWhisperMessage(serialized []byte) (*PreKeyWhisperMessage, error) { 149 | version := highBitsToInt(serialized[0]) 150 | 151 | if version != currentVersion { 152 | return nil, UnsupportedVersionError{version} 153 | } 154 | 155 | ppkwm := &protobuf.PreKeySignalMessage{} 156 | err := proto.Unmarshal(serialized[1:], ppkwm) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | if ppkwm.GetBaseKey() == nil || 162 | ppkwm.GetIdentityKey() == nil || 163 | ppkwm.GetMessage() == nil || 164 | ppkwm.GetSignedPreKeyId() == 0 { 165 | return nil, ErrIncompletePreKeyWhisperMessage 166 | } 167 | 168 | wm, err := LoadWhisperMessage(ppkwm.GetMessage()) 169 | if err != nil { 170 | return nil, err 171 | } 172 | pkwm := &PreKeyWhisperMessage{ 173 | Version: version, 174 | RegistrationID: ppkwm.GetRegistrationId(), 175 | PreKeyID: ppkwm.GetPreKeyId(), 176 | SignedPreKeyID: ppkwm.GetSignedPreKeyId(), 177 | BaseKey: NewECPublicKey(ppkwm.GetBaseKey()[1:]), 178 | IdentityKey: NewIdentityKey(ppkwm.GetIdentityKey()[1:]), 179 | Message: wm, 180 | serialized: serialized, 181 | } 182 | 183 | return pkwm, nil 184 | } 185 | 186 | func newPreKeyWhisperMessage(messageVersion byte, registrationID, preKeyID, signedPreKeyID uint32, baseKey *ECPublicKey, identityKey *IdentityKey, wm *WhisperMessage) (*PreKeyWhisperMessage, error) { 187 | 188 | ppkwm := &protobuf.PreKeySignalMessage{ 189 | RegistrationId: ®istrationID, 190 | PreKeyId: &preKeyID, 191 | SignedPreKeyId: &signedPreKeyID, 192 | BaseKey: baseKey.Serialize(), 193 | IdentityKey: identityKey.Serialize(), 194 | Message: wm.serialize(), 195 | } 196 | 197 | message, err := proto.Marshal(ppkwm) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | version := makeVersionByte(messageVersion, currentVersion) 203 | pkwm := &PreKeyWhisperMessage{ 204 | Version: version, 205 | RegistrationID: registrationID, 206 | PreKeyID: preKeyID, 207 | SignedPreKeyID: signedPreKeyID, 208 | BaseKey: baseKey, 209 | IdentityKey: identityKey, 210 | Message: wm, 211 | serialized: append([]byte{version}, message...), 212 | } 213 | return pkwm, nil 214 | } 215 | 216 | func (pkwm *PreKeyWhisperMessage) serialize() []byte { 217 | return pkwm.serialized 218 | } 219 | -------------------------------------------------------------------------------- /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 | protobuf "github.com/nanu-c/textsecure/axolotl/protobuf" 10 | "github.com/golang/protobuf/proto" 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/FingerprintProtocol.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: FingerprintProtocol.proto 3 | // DO NOT EDIT! 4 | 5 | package textsecure 6 | 7 | import proto "github.com/golang/protobuf/proto" 8 | import fmt "fmt" 9 | import math "math" 10 | 11 | // Reference imports to suppress errors if they are not otherwise used. 12 | var _ = proto.Marshal 13 | var _ = fmt.Errorf 14 | var _ = math.Inf 15 | 16 | type LogicalFingerprint struct { 17 | Content []byte `protobuf:"bytes,1,opt,name=content" json:"content,omitempty"` 18 | XXX_unrecognized []byte `json:"-"` 19 | } 20 | 21 | func (m *LogicalFingerprint) Reset() { *m = LogicalFingerprint{} } 22 | func (m *LogicalFingerprint) String() string { return proto.CompactTextString(m) } 23 | func (*LogicalFingerprint) ProtoMessage() {} 24 | func (*LogicalFingerprint) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } 25 | 26 | func (m *LogicalFingerprint) GetContent() []byte { 27 | if m != nil { 28 | return m.Content 29 | } 30 | return nil 31 | } 32 | 33 | type CombinedFingerprints struct { 34 | Version *uint32 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"` 35 | LocalFingerprint *LogicalFingerprint `protobuf:"bytes,2,opt,name=localFingerprint" json:"localFingerprint,omitempty"` 36 | RemoteFingerprint *LogicalFingerprint `protobuf:"bytes,3,opt,name=remoteFingerprint" json:"remoteFingerprint,omitempty"` 37 | XXX_unrecognized []byte `json:"-"` 38 | } 39 | 40 | func (m *CombinedFingerprints) Reset() { *m = CombinedFingerprints{} } 41 | func (m *CombinedFingerprints) String() string { return proto.CompactTextString(m) } 42 | func (*CombinedFingerprints) ProtoMessage() {} 43 | func (*CombinedFingerprints) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } 44 | 45 | func (m *CombinedFingerprints) GetVersion() uint32 { 46 | if m != nil && m.Version != nil { 47 | return *m.Version 48 | } 49 | return 0 50 | } 51 | 52 | func (m *CombinedFingerprints) GetLocalFingerprint() *LogicalFingerprint { 53 | if m != nil { 54 | return m.LocalFingerprint 55 | } 56 | return nil 57 | } 58 | 59 | func (m *CombinedFingerprints) GetRemoteFingerprint() *LogicalFingerprint { 60 | if m != nil { 61 | return m.RemoteFingerprint 62 | } 63 | return nil 64 | } 65 | 66 | func init() { 67 | proto.RegisterType((*LogicalFingerprint)(nil), "textsecure.LogicalFingerprint") 68 | proto.RegisterType((*CombinedFingerprints)(nil), "textsecure.CombinedFingerprints") 69 | } 70 | 71 | func init() { proto.RegisterFile("FingerprintProtocol.proto", fileDescriptor2) } 72 | 73 | var fileDescriptor2 = []byte{ 74 | // 207 bytes of a gzipped FileDescriptorProto 75 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x90, 0x3f, 0x4b, 0xc5, 0x40, 76 | 0x10, 0xc4, 0x39, 0x2d, 0x84, 0x55, 0xc1, 0x77, 0x58, 0xc4, 0x46, 0x1e, 0xaf, 0x4a, 0x75, 0x85, 77 | 0xbd, 0x8d, 0x82, 0x85, 0xbc, 0x42, 0xf2, 0x0d, 0x92, 0x73, 0x3d, 0x17, 0x2e, 0xb7, 0x61, 0x77, 78 | 0xfd, 0xf7, 0x15, 0xfd, 0x54, 0x12, 0x21, 0x78, 0x9a, 0xc6, 0x6e, 0x07, 0xe6, 0x37, 0x3b, 0x0c, 79 | 0x5c, 0xdc, 0x51, 0x49, 0x28, 0x93, 0x50, 0xb1, 0x07, 0x61, 0xe3, 0xc8, 0x39, 0x4c, 0xf3, 0xe1, 80 | 0xc1, 0xf0, 0xdd, 0x14, 0xe3, 0x8b, 0xe0, 0x2e, 0x80, 0xdf, 0x73, 0xa2, 0xd8, 0xe7, 0xca, 0xef, 81 | 0x1b, 0x38, 0x8a, 0x5c, 0x0c, 0x8b, 0x35, 0x6e, 0xeb, 0xda, 0x93, 0x6e, 0x91, 0xbb, 0x4f, 0x07, 82 | 0xe7, 0xb7, 0x3c, 0x0e, 0x54, 0xf0, 0xb1, 0x22, 0x74, 0x46, 0x5e, 0x51, 0x94, 0xb8, 0x7c, 0x23, 83 | 0xa7, 0xdd, 0x22, 0xfd, 0x3d, 0x9c, 0x65, 0xfe, 0xfd, 0xa0, 0x39, 0xd8, 0xba, 0xf6, 0xf8, 0xea, 84 | 0x32, 0xfc, 0x34, 0x09, 0xeb, 0x1a, 0xdd, 0x8a, 0xf3, 0x7b, 0xd8, 0x08, 0x8e, 0x6c, 0x58, 0x87, 85 | 0x1d, 0xfe, 0x2b, 0x6c, 0x0d, 0xde, 0x5c, 0x43, 0xcb, 0x92, 0xc2, 0xdb, 0x33, 0xe9, 0x84, 0xa2, 86 | 0x1f, 0x6a, 0x38, 0x6a, 0xc8, 0x34, 0x28, 0xa5, 0xd2, 0xe7, 0xf0, 0x54, 0x79, 0x37, 0x7f, 0xf7, 87 | 0xd4, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x44, 0x19, 0x98, 0x63, 0x01, 0x00, 0x00, 88 | } 89 | -------------------------------------------------------------------------------- /axolotl/protobuf/FingerprintProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libsignal.fingerprint"; 4 | option java_outer_classname = "FingerprintProtos"; 5 | 6 | message LogicalFingerprint { 7 | optional bytes content = 1; 8 | // optional bytes identifier = 2; 9 | } 10 | 11 | message CombinedFingerprints { 12 | optional uint32 version = 1; 13 | optional LogicalFingerprint localFingerprint = 2; 14 | optional LogicalFingerprint remoteFingerprint = 3; 15 | } -------------------------------------------------------------------------------- /axolotl/protobuf/LocalStorageProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libsignal.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 | } -------------------------------------------------------------------------------- /axolotl/protobuf/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | protoc --go_out=. WhisperTextProtocol.proto LocalStorageProtocol.proto FingerprintProtocol.proto 4 | 5 | clean: 6 | rm -f WhisperTextProtocol.pb.go LocalStorageProtocol.pb.go FingerprintProtocol.pb.go 7 | -------------------------------------------------------------------------------- /axolotl/protobuf/WhisperTextProtocol.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: WhisperTextProtocol.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package textsecure is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | WhisperTextProtocol.proto 10 | LocalStorageProtocol.proto 11 | FingerprintProtocol.proto 12 | 13 | It has these top-level messages: 14 | SignalMessage 15 | PreKeySignalMessage 16 | KeyExchangeMessage 17 | SenderKeyMessage 18 | SenderKeyDistributionMessage 19 | DeviceConsistencyCodeMessage 20 | SessionStructure 21 | RecordStructure 22 | PreKeyRecordStructure 23 | SignedPreKeyRecordStructure 24 | IdentityKeyPairStructure 25 | SenderKeyStateStructure 26 | SenderKeyRecordStructure 27 | LogicalFingerprint 28 | CombinedFingerprints 29 | */ 30 | package textsecure 31 | 32 | import proto "github.com/golang/protobuf/proto" 33 | import fmt "fmt" 34 | import math "math" 35 | 36 | // Reference imports to suppress errors if they are not otherwise used. 37 | var _ = proto.Marshal 38 | var _ = fmt.Errorf 39 | var _ = math.Inf 40 | 41 | // This is a compile-time assertion to ensure that this generated file 42 | // is compatible with the proto package it is being compiled against. 43 | // A compilation error at this line likely means your copy of the 44 | // proto package needs to be updated. 45 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 46 | 47 | type SignalMessage struct { 48 | RatchetKey []byte `protobuf:"bytes,1,opt,name=ratchetKey" json:"ratchetKey,omitempty"` 49 | Counter *uint32 `protobuf:"varint,2,opt,name=counter" json:"counter,omitempty"` 50 | PreviousCounter *uint32 `protobuf:"varint,3,opt,name=previousCounter" json:"previousCounter,omitempty"` 51 | Ciphertext []byte `protobuf:"bytes,4,opt,name=ciphertext" json:"ciphertext,omitempty"` 52 | XXX_unrecognized []byte `json:"-"` 53 | } 54 | 55 | func (m *SignalMessage) Reset() { *m = SignalMessage{} } 56 | func (m *SignalMessage) String() string { return proto.CompactTextString(m) } 57 | func (*SignalMessage) ProtoMessage() {} 58 | func (*SignalMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 59 | 60 | func (m *SignalMessage) GetRatchetKey() []byte { 61 | if m != nil { 62 | return m.RatchetKey 63 | } 64 | return nil 65 | } 66 | 67 | func (m *SignalMessage) GetCounter() uint32 { 68 | if m != nil && m.Counter != nil { 69 | return *m.Counter 70 | } 71 | return 0 72 | } 73 | 74 | func (m *SignalMessage) GetPreviousCounter() uint32 { 75 | if m != nil && m.PreviousCounter != nil { 76 | return *m.PreviousCounter 77 | } 78 | return 0 79 | } 80 | 81 | func (m *SignalMessage) GetCiphertext() []byte { 82 | if m != nil { 83 | return m.Ciphertext 84 | } 85 | return nil 86 | } 87 | 88 | type PreKeySignalMessage struct { 89 | RegistrationId *uint32 `protobuf:"varint,5,opt,name=registrationId" json:"registrationId,omitempty"` 90 | PreKeyId *uint32 `protobuf:"varint,1,opt,name=preKeyId" json:"preKeyId,omitempty"` 91 | SignedPreKeyId *uint32 `protobuf:"varint,6,opt,name=signedPreKeyId" json:"signedPreKeyId,omitempty"` 92 | BaseKey []byte `protobuf:"bytes,2,opt,name=baseKey" json:"baseKey,omitempty"` 93 | IdentityKey []byte `protobuf:"bytes,3,opt,name=identityKey" json:"identityKey,omitempty"` 94 | Message []byte `protobuf:"bytes,4,opt,name=message" json:"message,omitempty"` 95 | XXX_unrecognized []byte `json:"-"` 96 | } 97 | 98 | func (m *PreKeySignalMessage) Reset() { *m = PreKeySignalMessage{} } 99 | func (m *PreKeySignalMessage) String() string { return proto.CompactTextString(m) } 100 | func (*PreKeySignalMessage) ProtoMessage() {} 101 | func (*PreKeySignalMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 102 | 103 | func (m *PreKeySignalMessage) GetRegistrationId() uint32 { 104 | if m != nil && m.RegistrationId != nil { 105 | return *m.RegistrationId 106 | } 107 | return 0 108 | } 109 | 110 | func (m *PreKeySignalMessage) GetPreKeyId() uint32 { 111 | if m != nil && m.PreKeyId != nil { 112 | return *m.PreKeyId 113 | } 114 | return 0 115 | } 116 | 117 | func (m *PreKeySignalMessage) GetSignedPreKeyId() uint32 { 118 | if m != nil && m.SignedPreKeyId != nil { 119 | return *m.SignedPreKeyId 120 | } 121 | return 0 122 | } 123 | 124 | func (m *PreKeySignalMessage) GetBaseKey() []byte { 125 | if m != nil { 126 | return m.BaseKey 127 | } 128 | return nil 129 | } 130 | 131 | func (m *PreKeySignalMessage) GetIdentityKey() []byte { 132 | if m != nil { 133 | return m.IdentityKey 134 | } 135 | return nil 136 | } 137 | 138 | func (m *PreKeySignalMessage) GetMessage() []byte { 139 | if m != nil { 140 | return m.Message 141 | } 142 | return nil 143 | } 144 | 145 | type KeyExchangeMessage struct { 146 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 147 | BaseKey []byte `protobuf:"bytes,2,opt,name=baseKey" json:"baseKey,omitempty"` 148 | RatchetKey []byte `protobuf:"bytes,3,opt,name=ratchetKey" json:"ratchetKey,omitempty"` 149 | IdentityKey []byte `protobuf:"bytes,4,opt,name=identityKey" json:"identityKey,omitempty"` 150 | BaseKeySignature []byte `protobuf:"bytes,5,opt,name=baseKeySignature" json:"baseKeySignature,omitempty"` 151 | XXX_unrecognized []byte `json:"-"` 152 | } 153 | 154 | func (m *KeyExchangeMessage) Reset() { *m = KeyExchangeMessage{} } 155 | func (m *KeyExchangeMessage) String() string { return proto.CompactTextString(m) } 156 | func (*KeyExchangeMessage) ProtoMessage() {} 157 | func (*KeyExchangeMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 158 | 159 | func (m *KeyExchangeMessage) GetId() uint32 { 160 | if m != nil && m.Id != nil { 161 | return *m.Id 162 | } 163 | return 0 164 | } 165 | 166 | func (m *KeyExchangeMessage) GetBaseKey() []byte { 167 | if m != nil { 168 | return m.BaseKey 169 | } 170 | return nil 171 | } 172 | 173 | func (m *KeyExchangeMessage) GetRatchetKey() []byte { 174 | if m != nil { 175 | return m.RatchetKey 176 | } 177 | return nil 178 | } 179 | 180 | func (m *KeyExchangeMessage) GetIdentityKey() []byte { 181 | if m != nil { 182 | return m.IdentityKey 183 | } 184 | return nil 185 | } 186 | 187 | func (m *KeyExchangeMessage) GetBaseKeySignature() []byte { 188 | if m != nil { 189 | return m.BaseKeySignature 190 | } 191 | return nil 192 | } 193 | 194 | type SenderKeyMessage struct { 195 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 196 | Iteration *uint32 `protobuf:"varint,2,opt,name=iteration" json:"iteration,omitempty"` 197 | Ciphertext []byte `protobuf:"bytes,3,opt,name=ciphertext" json:"ciphertext,omitempty"` 198 | XXX_unrecognized []byte `json:"-"` 199 | } 200 | 201 | func (m *SenderKeyMessage) Reset() { *m = SenderKeyMessage{} } 202 | func (m *SenderKeyMessage) String() string { return proto.CompactTextString(m) } 203 | func (*SenderKeyMessage) ProtoMessage() {} 204 | func (*SenderKeyMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 205 | 206 | func (m *SenderKeyMessage) GetId() uint32 { 207 | if m != nil && m.Id != nil { 208 | return *m.Id 209 | } 210 | return 0 211 | } 212 | 213 | func (m *SenderKeyMessage) GetIteration() uint32 { 214 | if m != nil && m.Iteration != nil { 215 | return *m.Iteration 216 | } 217 | return 0 218 | } 219 | 220 | func (m *SenderKeyMessage) GetCiphertext() []byte { 221 | if m != nil { 222 | return m.Ciphertext 223 | } 224 | return nil 225 | } 226 | 227 | type SenderKeyDistributionMessage struct { 228 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 229 | Iteration *uint32 `protobuf:"varint,2,opt,name=iteration" json:"iteration,omitempty"` 230 | ChainKey []byte `protobuf:"bytes,3,opt,name=chainKey" json:"chainKey,omitempty"` 231 | SigningKey []byte `protobuf:"bytes,4,opt,name=signingKey" json:"signingKey,omitempty"` 232 | XXX_unrecognized []byte `json:"-"` 233 | } 234 | 235 | func (m *SenderKeyDistributionMessage) Reset() { *m = SenderKeyDistributionMessage{} } 236 | func (m *SenderKeyDistributionMessage) String() string { return proto.CompactTextString(m) } 237 | func (*SenderKeyDistributionMessage) ProtoMessage() {} 238 | func (*SenderKeyDistributionMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 239 | 240 | func (m *SenderKeyDistributionMessage) GetId() uint32 { 241 | if m != nil && m.Id != nil { 242 | return *m.Id 243 | } 244 | return 0 245 | } 246 | 247 | func (m *SenderKeyDistributionMessage) GetIteration() uint32 { 248 | if m != nil && m.Iteration != nil { 249 | return *m.Iteration 250 | } 251 | return 0 252 | } 253 | 254 | func (m *SenderKeyDistributionMessage) GetChainKey() []byte { 255 | if m != nil { 256 | return m.ChainKey 257 | } 258 | return nil 259 | } 260 | 261 | func (m *SenderKeyDistributionMessage) GetSigningKey() []byte { 262 | if m != nil { 263 | return m.SigningKey 264 | } 265 | return nil 266 | } 267 | 268 | type DeviceConsistencyCodeMessage struct { 269 | Generation *uint32 `protobuf:"varint,1,opt,name=generation" json:"generation,omitempty"` 270 | Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"` 271 | XXX_unrecognized []byte `json:"-"` 272 | } 273 | 274 | func (m *DeviceConsistencyCodeMessage) Reset() { *m = DeviceConsistencyCodeMessage{} } 275 | func (m *DeviceConsistencyCodeMessage) String() string { return proto.CompactTextString(m) } 276 | func (*DeviceConsistencyCodeMessage) ProtoMessage() {} 277 | func (*DeviceConsistencyCodeMessage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 278 | 279 | func (m *DeviceConsistencyCodeMessage) GetGeneration() uint32 { 280 | if m != nil && m.Generation != nil { 281 | return *m.Generation 282 | } 283 | return 0 284 | } 285 | 286 | func (m *DeviceConsistencyCodeMessage) GetSignature() []byte { 287 | if m != nil { 288 | return m.Signature 289 | } 290 | return nil 291 | } 292 | 293 | func init() { 294 | proto.RegisterType((*SignalMessage)(nil), "textsecure.SignalMessage") 295 | proto.RegisterType((*PreKeySignalMessage)(nil), "textsecure.PreKeySignalMessage") 296 | proto.RegisterType((*KeyExchangeMessage)(nil), "textsecure.KeyExchangeMessage") 297 | proto.RegisterType((*SenderKeyMessage)(nil), "textsecure.SenderKeyMessage") 298 | proto.RegisterType((*SenderKeyDistributionMessage)(nil), "textsecure.SenderKeyDistributionMessage") 299 | proto.RegisterType((*DeviceConsistencyCodeMessage)(nil), "textsecure.DeviceConsistencyCodeMessage") 300 | } 301 | 302 | func init() { proto.RegisterFile("WhisperTextProtocol.proto", fileDescriptor0) } 303 | 304 | var fileDescriptor0 = []byte{ 305 | // 446 bytes of a gzipped FileDescriptorProto 306 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x8e, 0xd3, 0x30, 307 | 0x10, 0x56, 0xda, 0x05, 0x96, 0xa1, 0x5d, 0x56, 0xe1, 0x12, 0x56, 0x15, 0xaa, 0x22, 0x81, 0x2a, 308 | 0x0e, 0xbd, 0xf1, 0x02, 0xdb, 0xe5, 0xb0, 0xaa, 0x90, 0xaa, 0x2c, 0x12, 0x17, 0x0e, 0xa4, 0xce, 309 | 0xc8, 0x19, 0xa9, 0x6b, 0x47, 0xb6, 0x53, 0x9a, 0x37, 0xe0, 0xce, 0x6b, 0xf0, 0x3e, 0xbc, 0x0e, 310 | 0xb2, 0x9d, 0xbf, 0x26, 0x82, 0x03, 0xb7, 0xcc, 0x97, 0xb1, 0xbf, 0x6f, 0xbe, 0xcf, 0x03, 0xaf, 311 | 0xbf, 0xe4, 0xa4, 0x0b, 0x54, 0x9f, 0xf1, 0x64, 0x76, 0x4a, 0x1a, 0xc9, 0xe4, 0x61, 0x5d, 0xd8, 312 | 0x8f, 0x10, 0x0c, 0x9e, 0x8c, 0x46, 0x56, 0x2a, 0x8c, 0x7f, 0x06, 0x30, 0x7f, 0x20, 0x2e, 0xd2, 313 | 0xc3, 0x27, 0xd4, 0x3a, 0xe5, 0x18, 0xbe, 0x01, 0x50, 0xa9, 0x61, 0x39, 0x9a, 0x2d, 0x56, 0x51, 314 | 0xb0, 0x0c, 0x56, 0xb3, 0xa4, 0x87, 0x84, 0x11, 0x3c, 0x63, 0xb2, 0x14, 0x06, 0x55, 0x34, 0x59, 315 | 0x06, 0xab, 0x79, 0xd2, 0x94, 0xe1, 0x0a, 0x5e, 0x16, 0x0a, 0x8f, 0x24, 0x4b, 0xbd, 0xa9, 0x3b, 316 | 0xa6, 0xae, 0x63, 0x08, 0x5b, 0x0e, 0x46, 0x45, 0x8e, 0xca, 0x2a, 0x89, 0x2e, 0x3c, 0x47, 0x87, 317 | 0xc4, 0xbf, 0x03, 0x78, 0xb5, 0x53, 0xb8, 0xc5, 0xea, 0x5c, 0xdb, 0x3b, 0xb8, 0x52, 0xc8, 0x49, 318 | 0x1b, 0x95, 0x1a, 0x92, 0xe2, 0x3e, 0x8b, 0x9e, 0x38, 0x82, 0x01, 0x1a, 0xde, 0xc0, 0x65, 0xe1, 319 | 0x8e, 0xdf, 0x67, 0x6e, 0x82, 0x79, 0xd2, 0xd6, 0xf6, 0x0e, 0x4d, 0x5c, 0x60, 0xb6, 0x6b, 0x3a, 320 | 0x9e, 0xfa, 0x3b, 0xce, 0x51, 0x3b, 0xe7, 0x3e, 0xd5, 0xb6, 0x70, 0x73, 0xce, 0x92, 0xa6, 0x0c, 321 | 0x97, 0xf0, 0x82, 0x32, 0x14, 0x86, 0x4c, 0x65, 0xff, 0x4e, 0xdd, 0xdf, 0x3e, 0x64, 0xcf, 0x3e, 322 | 0x7a, 0xc9, 0xf5, 0x70, 0x4d, 0x19, 0xff, 0x0a, 0x20, 0xdc, 0x62, 0xf5, 0xf1, 0xc4, 0xf2, 0x54, 323 | 0x70, 0x6c, 0x06, 0xbb, 0x82, 0x09, 0x35, 0x52, 0x27, 0xf4, 0x2f, 0xf2, 0xf3, 0x78, 0xa6, 0xa3, 324 | 0x78, 0x06, 0xe2, 0x2e, 0xc6, 0xe2, 0xde, 0xc3, 0x75, 0x7d, 0x99, 0x33, 0xd7, 0x94, 0x0a, 0x9d, 325 | 0x8d, 0xb3, 0x64, 0x84, 0xc7, 0xdf, 0xe0, 0xfa, 0x01, 0x45, 0x86, 0x6a, 0x8b, 0xd5, 0xdf, 0xb4, 326 | 0x2e, 0xe0, 0x39, 0x19, 0xf4, 0xde, 0xd7, 0x4f, 0xa2, 0x03, 0x06, 0x51, 0x4f, 0x47, 0x51, 0xff, 327 | 0x08, 0x60, 0xd1, 0x52, 0xdc, 0xd9, 0x10, 0x69, 0x5f, 0xda, 0x93, 0xff, 0x47, 0x77, 0x03, 0x97, 328 | 0x2c, 0x4f, 0x49, 0x74, 0xe6, 0xb4, 0xb5, 0x95, 0x62, 0x33, 0x26, 0xc1, 0x3b, 0x67, 0x7a, 0x48, 329 | 0xfc, 0x15, 0x16, 0x77, 0x78, 0x24, 0x86, 0x1b, 0x29, 0x34, 0x69, 0x83, 0x82, 0x55, 0x1b, 0x99, 330 | 0x61, 0x6f, 0x33, 0x38, 0x8a, 0x86, 0xda, 0x2b, 0xea, 0x21, 0x56, 0x99, 0x6e, 0x1d, 0xf5, 0xb1, 331 | 0x75, 0xc0, 0xed, 0x07, 0x78, 0x2b, 0x15, 0x5f, 0x7f, 0xf7, 0x6b, 0xa9, 0x2b, 0x6d, 0xf0, 0x51, 332 | 0xaf, 0x0f, 0xb4, 0x77, 0x2d, 0xf5, 0x6e, 0x32, 0x79, 0xb8, 0x9d, 0xf9, 0x37, 0xef, 0x96, 0x56, 333 | 0xff, 0x09, 0x00, 0x00, 0xff, 0xff, 0x32, 0x36, 0x08, 0x6d, 0xc8, 0x03, 0x00, 0x00, 334 | } 335 | -------------------------------------------------------------------------------- /axolotl/protobuf/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | package textsecure; 2 | 3 | option java_package = "org.whispersystems.libsignal.protocol"; 4 | option java_outer_classname = "SignalProtos"; 5 | 6 | message SignalMessage { 7 | optional bytes ratchetKey = 1; 8 | optional uint32 counter = 2; 9 | optional uint32 previousCounter = 3; 10 | optional bytes ciphertext = 4; 11 | } 12 | 13 | message PreKeySignalMessage { 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; // SignalMessage 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 | 43 | message DeviceConsistencyCodeMessage { 44 | optional uint32 generation = 1; 45 | optional bytes signature = 2; 46 | } -------------------------------------------------------------------------------- /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 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "os/exec" 15 | "path/filepath" 16 | "regexp" 17 | "strings" 18 | "time" 19 | "gopkg.in/yaml.v2" 20 | 21 | "github.com/nanu-c/textsecure" 22 | "github.com/nanu-c/textsecure/axolotl" 23 | "golang.org/x/crypto/ssh/terminal" 24 | ) 25 | 26 | // Simple command line test app for TextSecure. 27 | // It can act as an echo service, send one-off messages and attachments, 28 | // or carry on a conversation with another client 29 | 30 | var ( 31 | echo bool 32 | to string 33 | group bool 34 | message string 35 | attachment string 36 | newgroup string 37 | updategroup string 38 | leavegroup string 39 | endsession bool 40 | showdevices bool 41 | linkdevice string 42 | unlinkdevice int 43 | configDir string 44 | stress int 45 | hook string 46 | raw bool 47 | gateway bool 48 | bind string 49 | ) 50 | 51 | func init() { 52 | flag.BoolVar(&echo, "echo", false, "Act as an echo service") 53 | flag.StringVar(&to, "to", "", "Contact name to send the message to") 54 | flag.BoolVar(&group, "group", false, "Destination is a group") 55 | flag.StringVar(&message, "message", "", "Single message to send, then exit") 56 | flag.StringVar(&attachment, "attachment", "", "File to attach") 57 | flag.StringVar(&newgroup, "newgroup", "", "Create a group, the argument has the format 'name:member1:member2'") 58 | flag.StringVar(&updategroup, "updategroup", "", "Update a group, the argument has the format 'hexid:name:member1:member2'") 59 | flag.StringVar(&leavegroup, "leavegroup", "", "Leave a group named by the argument") 60 | flag.BoolVar(&endsession, "endsession", false, "Terminate session with peer") 61 | flag.BoolVar(&showdevices, "showdevices", false, "Show linked devices") 62 | flag.StringVar(&linkdevice, "linkdevice", "", "Link a new device, the argument is a url in the format 'tsdevice:/?uuid=xxx&pub_key=yyy'") 63 | flag.IntVar(&unlinkdevice, "unlinkdevice", 0, "Unlink a device, the argument is the id of the device to delete") 64 | flag.IntVar(&stress, "stress", 0, "Automatically send many messages to the peer") 65 | flag.StringVar(&configDir, "config", ".config", "Location of config dir") 66 | flag.StringVar(&hook, "hook", "", "Program/Script to call when message is received (e.g. for bot usage)") 67 | flag.BoolVar(&raw, "raw", false, "raw mode, disable ansi colors") 68 | flag.BoolVar(&gateway, "gateway", false, "http gateway mode") 69 | flag.StringVar(&bind, "bind", "localhost:5000", "bind address and port when in gateway-mode") 70 | } 71 | 72 | var ( 73 | red = "\x1b[31m" 74 | green = "\x1b[32m" 75 | yellow = "\x1b[33m" 76 | blue = "\x1b[34m" 77 | ) 78 | 79 | func readLine(prompt string) string { 80 | reader := bufio.NewReader(os.Stdin) 81 | fmt.Print(prompt) 82 | text, _, err := reader.ReadLine() 83 | if err != nil { 84 | log.Fatal("Cannot read line from console: ", err) 85 | } 86 | return string(text) 87 | } 88 | 89 | func getVerificationCode() string { 90 | return readLine("Enter verification code>") 91 | } 92 | 93 | func getStoragePassword() string { 94 | fmt.Printf("Input storage password>") 95 | password, err := terminal.ReadPassword(0) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | fmt.Println() 100 | return string(password) 101 | } 102 | 103 | func getConfig() (*textsecure.Config, error) { 104 | return textsecure.ReadConfig(configDir + "/config.yml") 105 | } 106 | 107 | func getLocalContacts() ([]textsecure.Contact, error) { 108 | return textsecure.ReadContacts(configDir + "/contacts.yml") 109 | } 110 | 111 | func sendMessage(isGroup bool, to, message string) error { 112 | var err error 113 | if isGroup { 114 | _, err = textsecure.SendGroupMessage(to, message) 115 | } else { 116 | _, err = textsecure.SendMessage(to, message) 117 | if nerr, ok := err.(axolotl.NotTrustedError); ok { 118 | fmt.Printf("Peer identity not trusted. Remove the file .storage/identity/remote_%s to approve\n", nerr.ID) 119 | } 120 | } 121 | return err 122 | } 123 | 124 | func sendAttachment(isGroup bool, to, message string, f io.Reader) error { 125 | var err error 126 | if isGroup { 127 | _, err = textsecure.SendGroupAttachment(to, message, f) 128 | } else { 129 | _, err = textsecure.SendAttachment(to, message, f) 130 | if nerr, ok := err.(axolotl.NotTrustedError); ok { 131 | fmt.Printf("Peer identity not trusted. Remove the file .storage/identity/remote_%s to approve\n", nerr.ID) 132 | } 133 | } 134 | return err 135 | } 136 | 137 | // conversationLoop sends messages read from the console 138 | func conversationLoop(isGroup bool) { 139 | for { 140 | var message string 141 | if raw { 142 | message = readLine(fmt.Sprintf("")) 143 | } else { 144 | message = readLine(fmt.Sprintf("%s>", blue)) 145 | } 146 | if message == "" { 147 | continue 148 | } 149 | 150 | err := sendMessage(isGroup, to, message) 151 | 152 | if err != nil { 153 | log.Println(err) 154 | } 155 | } 156 | } 157 | 158 | func messageHandler(msg *textsecure.Message) { 159 | if echo { 160 | to := msg.Source() 161 | if msg.Group() != nil { 162 | to = msg.Group().Hexid 163 | } 164 | err := sendMessage(msg.Group() != nil, to, msg.Message()) 165 | 166 | if err != nil { 167 | log.Println(err) 168 | } 169 | return 170 | } 171 | 172 | if msg.Message() != "" { 173 | fmt.Printf("\r %s%s\n>", pretty(msg), blue) 174 | if hook != "" { 175 | hookProcess := exec.Command(hook,pretty(msg)) 176 | hookProcess.Start() 177 | hookProcess.Wait() 178 | } 179 | if ! raw { 180 | fmt.Printf("\r %s%s\n>", pretty(msg), blue) 181 | } 182 | } 183 | 184 | for _, a := range msg.Attachments() { 185 | handleAttachment(msg.Source(), a.R) 186 | } 187 | 188 | // if no peer was specified on the command line, start a conversation with the first one contacting us 189 | if to == "" { 190 | to = msg.Source() 191 | isGroup := false 192 | if msg.Group() != nil { 193 | isGroup = true 194 | to = msg.Group().Hexid 195 | } 196 | go conversationLoop(isGroup) 197 | } 198 | } 199 | 200 | func handleAttachment(src string, r io.Reader) { 201 | f, err := ioutil.TempFile(".", "TextSecure_Attachment") 202 | if err != nil { 203 | log.Println(err) 204 | return 205 | } 206 | l, err := io.Copy(f, r) 207 | if err != nil { 208 | log.Println(err) 209 | return 210 | } 211 | log.Printf("Saving attachment of length %d from %s to %s", l, src, f.Name()) 212 | } 213 | 214 | var timeFormat = "Mon 03:04" 215 | 216 | func timestamp(msg *textsecure.Message) string { 217 | t := time.Unix(0, int64(msg.Timestamp())*1000000) 218 | return t.Format(timeFormat) 219 | } 220 | 221 | func pretty(msg *textsecure.Message) string { 222 | src := getName(msg.Source()) 223 | if msg.Group() != nil { 224 | src = src + "[" + msg.Group().Name + "]" 225 | } 226 | if raw { 227 | return fmt.Sprintf("%s %s %s", timestamp(msg), src, msg.Message()) 228 | } 229 | return fmt.Sprintf("%s%s %s%s %s%s", yellow, timestamp(msg), red, src, green, msg.Message()) 230 | } 231 | 232 | // getName returns the local contact name corresponding to a phone number, 233 | // or failing to find a contact the phone number itself 234 | func getName(tel string) string { 235 | if n, ok := telToName[tel]; ok { 236 | return n 237 | } 238 | return tel 239 | } 240 | 241 | func registrationDone() { 242 | log.Println("Registration done.") 243 | } 244 | 245 | // GroupFile loads group info from file 246 | type GroupFile struct { 247 | ID []byte 248 | Hexid string 249 | Flags uint32 250 | Name string 251 | Members []string 252 | Avatar io.Reader `yaml:"-"` 253 | } 254 | 255 | // Group as json object for output 256 | type Group struct { 257 | Name string `json:"name"` 258 | } 259 | 260 | // GroupsHandler will return all known groups as json 261 | func GroupsHandler(w http.ResponseWriter, r *http.Request) { 262 | w.Header().Set("Content-Type","application/json") 263 | 264 | data := make(map[string]Group) 265 | 266 | filepath.Walk(".storage/groups", func(path string, info os.FileInfo, e error) error { 267 | if info.Mode().IsRegular() { 268 | b, err := ioutil.ReadFile(path) 269 | if err != nil { 270 | return err 271 | } 272 | 273 | group := &GroupFile{} 274 | err = yaml.Unmarshal(b, group) 275 | if err != nil { 276 | return err 277 | } 278 | data[group.Hexid] = Group{Name: group.Name} 279 | } 280 | return nil 281 | }) 282 | json.NewEncoder(w).Encode(data) 283 | } 284 | 285 | // RekeyHandler will delete existing peer identity 286 | func RekeyHandler(w http.ResponseWriter, r *http.Request) { 287 | w.Header().Set("Content-Type","application/json") 288 | 289 | if r.Method != "DELETE" { 290 | w.WriteHeader(http.StatusInternalServerError) 291 | fmt.Fprintf(w, "{\"success\": false}") 292 | return 293 | } 294 | 295 | identity := r.URL.Path[len("/rekey/"):] 296 | isIdentity := regexp.MustCompile(`^\d*$`).MatchString(identity) 297 | if isIdentity { 298 | filename := []string{".storage/identity/remote", identity} 299 | err := os.Remove(strings.Join(filename, "_")) 300 | if err != nil { 301 | w.WriteHeader(http.StatusInternalServerError) 302 | fmt.Fprintf(w, "{\"success\": false, \"error\": \"identity %s not found\"}", identity) 303 | return 304 | } 305 | fmt.Fprintf(w, "{\"success\": true}") 306 | return 307 | } 308 | w.WriteHeader(http.StatusInternalServerError) 309 | fmt.Fprintf(w, "{\"success\": false}") 310 | return 311 | } 312 | 313 | // GatewaySend sends pre-processed messages 314 | func GatewaySend(to string, message string, filename string) (bool, string) { 315 | var errormessage string 316 | var err error 317 | isGroup := regexp.MustCompile(`^([a-fA-F\d]{32})$`).MatchString(to) 318 | if filename != "" { 319 | f, fErr := os.Open(filename) 320 | if fErr != nil { 321 | return false, fErr.Error() 322 | } 323 | err = sendAttachment(isGroup, to, message, f) 324 | os.Remove(filename) 325 | } else { 326 | err = sendMessage(isGroup, to, message) 327 | } 328 | if err != nil { 329 | switch { 330 | case regexp.MustCompile(`status code 413`).MatchString(err.Error()): 331 | errormessage = "signal api rate limit reached" 332 | case regexp.MustCompile(`remote identity \d+ is not trusted`).MatchString(err.Error()): 333 | errormessage = "remote identity " 334 | errormessage += to 335 | errormessage += " is not trusted" 336 | default: 337 | errormessage = strings.Trim(err.Error(), "\n") 338 | } 339 | return false, errormessage 340 | } 341 | return true, "OK" 342 | } 343 | 344 | // JSONHandler to receive POST json request, process and send 345 | func JSONHandler(w http.ResponseWriter, r *http.Request) { 346 | messageField := os.Getenv("JSON_MESSAGE") 347 | if messageField == "" { 348 | messageField = "message" 349 | } 350 | body, err := ioutil.ReadAll(r.Body) 351 | if err != nil { 352 | log.Println("Error: ", err.Error()) 353 | w.WriteHeader(http.StatusInternalServerError) 354 | fmt.Fprintf(w, "{\"success\": false}") 355 | return 356 | } 357 | var data map[string]interface{} 358 | result := json.Unmarshal([]byte(body), &data) 359 | if result != nil { 360 | log.Println("Error: ", result.Error()) 361 | w.WriteHeader(http.StatusInternalServerError) 362 | fmt.Fprintf(w, "{\"success\": false}") 363 | return 364 | } 365 | message := data[messageField] 366 | if message == nil { 367 | log.Println("Error: json request contains empty item ", messageField) 368 | w.WriteHeader(http.StatusInternalServerError) 369 | fmt.Fprintf(w, "{\"success\": false, \"error\": \"json request contains empty item %s\"}", messageField) 370 | return 371 | } 372 | to := r.URL.Path[len("/json/"):] 373 | sendError, errormessage := GatewaySend(to, message.(string), "") 374 | if sendError == false { 375 | log.Println("Error: ", errormessage) 376 | w.WriteHeader(http.StatusInternalServerError) 377 | fmt.Fprintf(w, "{\"success\": false, \"error\": \"%s\"}", errormessage) 378 | return 379 | } 380 | w.WriteHeader(http.StatusOK) 381 | fmt.Fprintf(w, "{\"success\": true}") 382 | return 383 | } 384 | 385 | // GatewayHandler to receive POST form data, process and send 386 | func GatewayHandler(w http.ResponseWriter, r *http.Request) { 387 | 388 | filename := "" 389 | 390 | w.Header().Set("Content-Type","application/json") 391 | 392 | if r.Method != "POST" { 393 | log.Println("Error: requires POST request") 394 | w.WriteHeader(http.StatusInternalServerError) 395 | fmt.Fprintf(w, "{\"success\": false}") 396 | return 397 | } 398 | 399 | message := r.FormValue("message") 400 | to := r.FormValue("to") 401 | file, header, err := r.FormFile("file") 402 | if err == nil { 403 | defer file.Close() 404 | f, err := os.OpenFile("/tmp/" + header.Filename, os.O_WRONLY|os.O_CREATE, 0666) 405 | if err != nil { 406 | log.Println("Error: ", err.Error()) 407 | } else { 408 | filename = "/tmp/" + header.Filename 409 | } 410 | defer f.Close() 411 | io.Copy(f, file) 412 | } 413 | 414 | if len(message) > 0 && len(to) > 0 { 415 | sendError := false 416 | errormessage := "OK" 417 | if filename != "" { 418 | sendError, errormessage = GatewaySend(to, message, filename) 419 | } else { 420 | sendError, errormessage = GatewaySend(to, message, "") 421 | } 422 | if sendError == false { 423 | log.Println("Error: ", errormessage) 424 | w.WriteHeader(http.StatusInternalServerError) 425 | fmt.Fprintf(w, "{\"success\": false, \"error\": \"%s\"}", errormessage) 426 | return 427 | } 428 | w.WriteHeader(http.StatusOK) 429 | fmt.Fprintf(w, "{\"success\": true}") 430 | return 431 | } 432 | log.Println("Error: form fields message and to are required") 433 | w.WriteHeader(http.StatusInternalServerError) 434 | fmt.Fprintf(w, "{\"success\": false, \"error\": \"form fields message and to are required\"}") 435 | return 436 | } 437 | 438 | var telToName map[string]string 439 | 440 | func main() { 441 | flag.Parse() 442 | log.SetFlags(0) 443 | client := &textsecure.Client{ 444 | GetConfig: getConfig, 445 | GetLocalContacts: getLocalContacts, 446 | GetVerificationCode: getVerificationCode, 447 | GetStoragePassword: getStoragePassword, 448 | MessageHandler: messageHandler, 449 | RegistrationDone: registrationDone, 450 | } 451 | err := textsecure.Setup(client) 452 | if err != nil { 453 | log.Fatal(err) 454 | } 455 | 456 | if gateway { 457 | http.HandleFunc("/", GatewayHandler) 458 | http.HandleFunc("/groups", GroupsHandler) 459 | http.HandleFunc("/rekey/", RekeyHandler) 460 | http.HandleFunc("/json/", JSONHandler) 461 | log.Fatal(http.ListenAndServe(bind, nil)) 462 | } 463 | 464 | if linkdevice != "" { 465 | log.Printf("Linking new device with url: %s", linkdevice) 466 | url, err := url.Parse(linkdevice) 467 | if err != nil { 468 | log.Fatal(err) 469 | } 470 | 471 | uuid := url.Query().Get("uuid") 472 | pk := url.Query().Get("pub_key") 473 | code, err := textsecure.NewDeviceVerificationCode() 474 | if err != nil { 475 | log.Fatal(err) 476 | } 477 | 478 | err = textsecure.AddDevice(uuid, pk, code) 479 | if err != nil { 480 | log.Fatal(err) 481 | } 482 | return 483 | } 484 | 485 | if unlinkdevice > 0 { 486 | err = textsecure.UnlinkDevice(unlinkdevice) 487 | if err != nil { 488 | log.Fatal(err) 489 | } 490 | return 491 | } 492 | 493 | if showdevices { 494 | devs, err := textsecure.LinkedDevices() 495 | if err != nil { 496 | log.Fatal(err) 497 | } 498 | 499 | for _, d := range devs { 500 | log.Printf("ID: %d\n", d.ID) 501 | log.Printf("Name: %s\n", d.Name) 502 | log.Printf("Created: %d\n", d.Created) 503 | log.Printf("LastSeen: %d\n", d.LastSeen) 504 | log.Println("============") 505 | } 506 | return 507 | } 508 | 509 | if !echo { 510 | contacts, err := textsecure.GetRegisteredContacts() 511 | if err != nil { 512 | log.Printf("Could not get contacts: %s\n", err) 513 | } 514 | 515 | telToName = make(map[string]string) 516 | for _, c := range contacts { 517 | telToName[c.Tel] = c.Name 518 | } 519 | 520 | if newgroup != "" { 521 | s := strings.Split(newgroup, ":") 522 | textsecure.NewGroup(s[0], s[1:]) 523 | return 524 | } 525 | if updategroup != "" { 526 | s := strings.Split(updategroup, ":") 527 | textsecure.UpdateGroup(s[0], s[1], s[2:]) 528 | return 529 | } 530 | if leavegroup != "" { 531 | textsecure.LeaveGroup(leavegroup) 532 | return 533 | } 534 | // If "to" matches a contact name then get its phone number, otherwise assume "to" is a phone number 535 | for _, c := range contacts { 536 | if strings.EqualFold(c.Name, to) { 537 | to = c.Tel 538 | break 539 | } 540 | } 541 | 542 | if to != "" { 543 | // Terminate the session with the peer 544 | if endsession { 545 | textsecure.EndSession(to, "TERMINATE") 546 | return 547 | } 548 | 549 | // Send attachment with optional message then exit 550 | if attachment != "" { 551 | f, err := os.Open(attachment) 552 | if err != nil { 553 | log.Fatal(err) 554 | } 555 | 556 | err = sendAttachment(group, to, message, f) 557 | if err != nil { 558 | log.Fatal(err) 559 | } 560 | return 561 | } 562 | 563 | if stress > 0 { 564 | c := make(chan int, stress) 565 | for i := 0; i < stress; i++ { 566 | go func(i int) { 567 | sendMessage(false, to, fmt.Sprintf("Stress %d\n", i)) 568 | c <- i 569 | }(i) 570 | } 571 | for i := 0; i < stress; i++ { 572 | <-c 573 | } 574 | return 575 | } 576 | 577 | // Send a message then exit 578 | if message != "" { 579 | err := sendMessage(group, to, message) 580 | if err != nil { 581 | log.Fatal(err) 582 | } 583 | return 584 | } 585 | 586 | // Enter conversation mode 587 | go conversationLoop(group) 588 | } 589 | } 590 | 591 | err = textsecure.StartListening() 592 | if err != nil { 593 | log.Println(err) 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /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 | "github.com/go-yaml/yaml" 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 | "github.com/nanu-c/textsecure/protobuf" 10 | log "github.com/sirupsen/logrus" 11 | 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | // Contact contains information about a contact. 16 | type Contact struct { 17 | Tel string 18 | Uuid string 19 | Name string 20 | Color string 21 | Avatar []byte 22 | Blocked bool 23 | ExpireTimer uint32 24 | } 25 | 26 | type yamlContacts struct { 27 | Contacts []Contact 28 | } 29 | 30 | var ( 31 | contactsFile string 32 | contacts = map[string]Contact{} 33 | ) 34 | 35 | // ReadContacts reads a YAML contacts file 36 | func loadContacts(contactsYaml *yamlContacts) { 37 | for _, c := range contactsYaml.Contacts { 38 | contacts[c.Tel] = c 39 | } 40 | } 41 | 42 | var filePath string 43 | 44 | func ReadContacts(fileName string) ([]Contact, error) { 45 | b, err := ioutil.ReadFile(fileName) 46 | filePath = fileName 47 | if err != nil { 48 | return nil, err 49 | } 50 | contactsYaml := &yamlContacts{} 51 | err = yaml.Unmarshal(b, contactsYaml) 52 | if err != nil { 53 | return nil, err 54 | } 55 | loadContacts(contactsYaml) 56 | return contactsYaml.Contacts, nil 57 | } 58 | 59 | // WriteContacts saves a list of contacts to a file 60 | func WriteContacts(filename string, contacts2 []Contact) error { 61 | c := &yamlContacts{contacts2} 62 | // func WriteContacts(filename string) error { 63 | b, err := yaml.Marshal(c) 64 | if err != nil { 65 | return err 66 | } 67 | return ioutil.WriteFile(filename, b, 0600) 68 | } 69 | func WriteContactsToPath() error { 70 | c := contactsToYaml() 71 | b, err := yaml.Marshal(c) 72 | if err != nil { 73 | return err 74 | } 75 | return ioutil.WriteFile(filePath, b, 0600) 76 | } 77 | func contactsToYaml() *yamlContacts { 78 | c := &yamlContacts{} 79 | for _, co := range contacts { 80 | c.Contacts = append(c.Contacts, co) 81 | } 82 | return c 83 | } 84 | 85 | // type AvatarDetail struct { 86 | // Length 87 | // } 88 | 89 | func updateContact(c *signalservice.ContactDetails) error { 90 | log.Debugln("[textsecure] updateContact ", c.GetName()) 91 | 92 | // var r io.Reader 93 | // av := c.GetAvatar() 94 | // buf := new(bytes.Buffer) 95 | // if av != nil { 96 | // att, err := handleSingleAttachment(av) 97 | // if err != nil { 98 | // return err 99 | // } 100 | // r = att.R 101 | // buf.ReadFrom(r) 102 | // } 103 | 104 | contacts[c.GetNumber()] = Contact{ 105 | Tel: c.GetNumber(), 106 | Uuid: c.GetUuid(), 107 | Name: c.GetName(), 108 | Color: c.GetColor(), 109 | Avatar: []byte(""), 110 | Blocked: c.GetBlocked(), 111 | ExpireTimer: c.GetExpireTimer(), 112 | } 113 | return WriteContactsToPath() 114 | } 115 | func handleContacts(src string, dm *signalservice.DataMessage) ([]*signalservice.DataMessage_Contact, error) { 116 | cs := dm.GetContact() 117 | if cs == nil { 118 | return nil, nil 119 | } 120 | 121 | for _, c := range cs { 122 | 123 | log.Debugln("[textsecure] handle Contact", c.GetName()) 124 | } 125 | // switch c.GetType() { 126 | // case signalservice.GroupContext_UPDATE: 127 | // if err := updateGroup(gr); err != nil { 128 | // return nil, err 129 | // } 130 | // groups[hexid].Flags = GroupUpdateFlag 131 | // case signalservice.GroupContext_DELIVER: 132 | // if _, ok := groups[hexid]; !ok { 133 | // g, _ := newPartlyGroup(gr.GetId()) 134 | // RequestGroupInfo(g) 135 | // setupGroups() 136 | // return nil, UnknownGroupIDError{hexid} 137 | // } 138 | // groups[hexid].Flags = 0 139 | // case signalservice.GroupContext_QUIT: 140 | // if err := quitGroup(src, hexid); err != nil { 141 | // return nil, err 142 | // } 143 | // groups[hexid].Flags = GroupLeaveFlag 144 | // } 145 | 146 | return nil, nil 147 | } 148 | func RequestContactInfo() error { 149 | var t signalservice.SyncMessage_Request_Type 150 | t = 1 151 | omsg := &signalservice.SyncMessage{ 152 | Request: &signalservice.SyncMessage_Request{ 153 | Type: &t, 154 | }, 155 | } 156 | _, err := sendSyncMessage(omsg) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /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/nanu-c/textsecure/axolotl" 20 | "github.com/nanu-c/textsecure/protobuf" 21 | "github.com/golang/protobuf/proto" 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 *signalservice.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 := &signalservice.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 | -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | https://github.com/signalapp/Signal-Android/blob/2882ef6d9f80eb011aeee908b61817602857d210/src/org/thoughtcrime/securesms/RegistrationActivity.java 2 | 3 | ``` 4 | import org.thoughtcrime.securesms.util.Util; 5 | String password = Util.getSecret(18); 6 | 7 | public static String getSecret(int size) { 8 | byte[] secret = getSecretBytes(size); 9 | return Base64.encodeBytes(secret); 10 | } 11 | 12 | public static byte[] getSecretBytes(int size) { 13 | byte[] secret = new byte[size]; 14 | getSecureRandom().nextBytes(secret); 15 | return secret; 16 | } 17 | 18 | public static SecureRandom getSecureRandom() { 19 | return new SecureRandom(); 20 | } 21 | ``` 22 | 23 | *in golang* 24 | 25 | ``` 26 | func GenerateRandomString(s int) (string, error) { 27 | b, err := GenerateRandomBytes(s) 28 | return base64.URLEncoding.EncodeToString(b), err 29 | } 30 | ``` 31 | *securesms/RegistrationActivity.java* 32 | ``` 33 | import org.whispersystems.libsignal.util.KeyHelper; 34 | int registrationId = KeyHelper.generateRegistrationId(false) 35 | String signalingKey = Util.getSecret(52); 36 | accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin); 37 | ``` 38 | 39 | org.whispersystems.libsignal.util.KeyHelper 40 | 41 | https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/util/KeyHelper.java 42 | 43 | ``` 44 | public static int generateRegistrationId(boolean extendedRange) { 45 | try { 46 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 47 | if (extendedRange) return secureRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 48 | else return secureRandom.nextInt(16380) + 1; 49 | } catch (NoSuchAlgorithmException e) { 50 | throw new AssertionError(e); 51 | } 52 | } 53 | ``` 54 | means get a random number between 1 and 16380 55 | 56 | *securesms/RegistrationActivity.java* 57 | 58 | ``` 59 | private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException { 60 | int registrationId = KeyHelper.generateRegistrationId(false); 61 | TextSecurePreferences.setLocalRegistrationId(RegistrationActivity.this, registrationId); 62 | SessionUtil.archiveAllSessions(RegistrationActivity.this); 63 | 64 | String signalingKey = Util.getSecret(52); 65 | 66 | accountManager.verifyAccountWithCode(code, signalingKey, registrationId, !registrationState.gcmToken.isPresent(), pin); 67 | ``` 68 | 69 | The real registratation 70 | 71 | ``` 72 | import org.whispersystems.signalservice.api.SignalServiceAccountManager; 73 | private SignalServiceAccountManager accountManager; 74 | 75 | ``` 76 | 77 | accountManager.verifyAccountWithCode( 78 | 79 | https://github.com/signalapp/libsignal-service-java/blob/master/java/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java 80 | ``` 81 | import org.whispersystems.signalservice.internal.push.PushServiceSocket; 82 | 83 | public void verifyAccountWithCode( 84 | String verificationCode, 85 | String signalingKey, 86 | int signalProtocolRegistrationId, 87 | boolean fetchesMessages, 88 | String pin, 89 | byte[] unidentifiedAccessKey, 90 | boolean unrestrictedUnidentifiedAccess 91 | ) 92 | throws IOException 93 | { 94 | this.pushServiceSocket.verifyAccountCode(verificationCode, 95 | signalingKey, 96 | signalProtocolRegistrationId, 97 | fetchesMessages, 98 | pin, 99 | unidentifiedAccessKey, sunrestrictedUnidentifiedAccess); 100 | } 101 | ``` 102 | 103 | *pushServiceSocket.verifyAccountCode(* 104 | 105 | ``` 106 | private static final String VERIFY_ACCOUNT_CODE_PATH = "/v1/accounts/code/%s"; 107 | 108 | public void verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages, String pin, 109 | byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess) 110 | throws IOException 111 | { 112 | AccountAttributes signalingKeyEntity = new AccountAttributes( 113 | signalingKey, 114 | registrationId, 115 | fetchesMessages, 116 | pin, 117 | unidentifiedAccessKey, 118 | unrestrictedUnidentifiedAccess); 119 | makeServiceRequest(String.format(VERIFY_ACCOUNT_CODE_PATH, verificationCode), 120 | "PUT", JsonUtil.toJson(signalingKeyEntity)); 121 | } 122 | ``` 123 | 124 | # captcha 125 | 126 | ``` 127 | import org.thoughtcrime.securesms.registration.PushChallengeRequest; 128 | Optional pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, fcmToken, e164number, PUSH_REQUEST_TIMEOUT_MS); 129 | 130 | accountManager.requestSmsVerificationCode(smsRetrieverSupported, registrationState.captchaToken, pushChallenge); 131 | ``` 132 | -------------------------------------------------------------------------------- /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 | "bytes" 8 | "encoding/hex" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | 16 | log "github.com/sirupsen/logrus" 17 | 18 | "github.com/nanu-c/textsecure/protobuf" 19 | "gopkg.in/yaml.v2" 20 | ) 21 | 22 | // Group holds group metadata. 23 | type Group struct { 24 | ID []byte 25 | Hexid string 26 | Flags uint32 27 | Name string 28 | Members []string 29 | Avatar []byte 30 | } 31 | 32 | var ( 33 | groupDir string 34 | groups = map[string]*Group{} 35 | ) 36 | 37 | // idToHex returns the hex representation of the group id byte-slice 38 | // to be used as both keys in the map and for naming the files. 39 | func idToHex(id []byte) string { 40 | return hex.EncodeToString(id) 41 | } 42 | 43 | // idToPath returns the path of the file for storing a group's state 44 | func idToPath(hexid string) string { 45 | return filepath.Join(groupDir, hexid) 46 | } 47 | 48 | // FIXME: for now using unencrypted YAML files for group state, 49 | // should be definitely encrypted and maybe another format. 50 | 51 | // saveGroup stores a group's state in a file. 52 | func saveGroup(hexid string) error { 53 | b, err := yaml.Marshal(groups[hexid]) 54 | if err != nil { 55 | return err 56 | } 57 | return ioutil.WriteFile(idToPath(hexid), b, 0600) 58 | } 59 | 60 | // loadGroup loads a group's state from a file. 61 | func loadGroup(path string) error { 62 | _, hexid := filepath.Split(path) 63 | b, err := ioutil.ReadFile(path) 64 | if err != nil { 65 | return err 66 | } 67 | 68 | group := &Group{} 69 | err = yaml.Unmarshal(b, group) 70 | if err != nil { 71 | return err 72 | } 73 | groups[hexid] = group 74 | return nil 75 | } 76 | func RemoveGroupKey(hexid string) error { 77 | err := os.Remove(config.StorageDir + "/groups/" + hexid) 78 | if err != nil { 79 | return err 80 | } 81 | return nil 82 | 83 | } 84 | 85 | // setupGroups reads all groups' state from storage. 86 | func setupGroups() error { 87 | groupDir = filepath.Join(config.StorageDir, "groups") 88 | if err := os.MkdirAll(groupDir, 0700); err != nil { 89 | return err 90 | } 91 | filepath.Walk(groupDir, func(path string, fi os.FileInfo, err error) error { 92 | if !fi.IsDir() { 93 | if !strings.Contains(path, "avatar") { 94 | loadGroup(path) 95 | } 96 | } 97 | return nil 98 | 99 | }) 100 | return nil 101 | } 102 | 103 | // removeMember removes a given number from a list. 104 | func removeMember(tel string, members []string) []string { 105 | for i, m := range members { 106 | if m == tel { 107 | members = append(members[:i], members[i+1:]...) 108 | break 109 | } 110 | } 111 | return members 112 | } 113 | 114 | // updateGroup updates a group's state based on an incoming message. 115 | func updateGroup(gr *signalservice.GroupContext) error { 116 | log.Debugln("[textsecure] updateGroup ", gr.GetName()) 117 | hexid := idToHex(gr.GetId()) 118 | 119 | var r io.Reader 120 | av := gr.GetAvatar() 121 | buf := new(bytes.Buffer) 122 | if av != nil { 123 | att, err := handleSingleAttachment(av) 124 | if err != nil { 125 | return err 126 | } 127 | r = att.R 128 | buf.ReadFrom(r) 129 | } 130 | 131 | groups[hexid] = &Group{ 132 | ID: gr.GetId(), 133 | Hexid: hexid, 134 | Name: gr.GetName(), 135 | Members: gr.GetMembersE164(), 136 | Avatar: buf.Bytes(), 137 | } 138 | return saveGroup(hexid) 139 | } 140 | 141 | // UnknownGroupIDError is returned when an unknown group id is encountered 142 | type UnknownGroupIDError struct { 143 | id string 144 | } 145 | 146 | func (err UnknownGroupIDError) Error() string { 147 | return fmt.Sprintf("unknown group ID %s", err.id) 148 | } 149 | 150 | // quitGroup removes a quitting member from the local group state. 151 | func quitGroup(src string, hexid string) error { 152 | gr, ok := groups[hexid] 153 | if !ok { 154 | return UnknownGroupIDError{hexid} 155 | } 156 | 157 | gr.Members = removeMember(src, gr.Members) 158 | 159 | return saveGroup(hexid) 160 | } 161 | 162 | // GroupUpdateFlag signals that this message updates the group membership or name. 163 | var GroupUpdateFlag uint32 = 1 164 | 165 | // GroupLeavelag signals that this message is a group leave message 166 | var GroupLeaveFlag uint32 = 2 167 | 168 | // handleGroups is the main entry point for handling the group metadata on messages. 169 | func handleGroups(src string, dm *signalservice.DataMessage) (*Group, error) { 170 | gr := dm.GetGroup() 171 | if gr == nil { 172 | return nil, nil 173 | } 174 | hexid := idToHex(gr.GetId()) 175 | log.Debugln("[textsecure] handle group", hexid, gr.GetType()) 176 | switch gr.GetType() { 177 | case signalservice.GroupContext_UPDATE: 178 | if err := updateGroup(gr); err != nil { 179 | return nil, err 180 | } 181 | groups[hexid].Flags = GroupUpdateFlag 182 | case signalservice.GroupContext_DELIVER: 183 | eGr, ok := groups[hexid] 184 | setupGroups() 185 | if !ok || len(eGr.Members) == 0 || hexid == eGr.Name { 186 | log.Debugln("[textsecure] request update group", hexid) 187 | g, _ := newPartlyGroup(gr.GetId()) 188 | g.Members = []string{src} 189 | RequestGroupInfo(g) 190 | setupGroups() 191 | return nil, UnknownGroupIDError{hexid} 192 | } 193 | groups[hexid].Flags = 0 194 | case signalservice.GroupContext_QUIT: 195 | if err := quitGroup(src, hexid); err != nil { 196 | return nil, err 197 | } 198 | groups[hexid].Flags = GroupLeaveFlag 199 | } 200 | 201 | return groups[hexid], nil 202 | } 203 | 204 | type groupMessage struct { 205 | id []byte 206 | name string 207 | members []string 208 | typ signalservice.GroupContext_Type 209 | } 210 | 211 | func sendGroupHelper(hexid string, msg string, a *att) (uint64, error) { 212 | var ts uint64 213 | var err error 214 | g, ok := groups[hexid] 215 | if !ok { 216 | log.Infoln("[textsecure] sendGroupHelper unknown group id") 217 | return 0, UnknownGroupIDError{hexid} 218 | } 219 | // if len is 0 smth is obviously wrong 220 | if len(g.Members) == 0 { 221 | err := RemoveGroupKey(hexid) 222 | if err != nil { 223 | log.Errorln("[textsecure] sendGroupHelper", err) 224 | } 225 | setupGroups() 226 | log.Infoln("[textsecure] sendGroupHelper", g) 227 | RequestGroupInfo(g) 228 | return 0, fmt.Errorf("[textsecure] sendGroupHelper: need someone in the group to send you a message") 229 | } 230 | for _, m := range g.Members { 231 | if m != config.Tel { 232 | omsg := &outgoingMessage{ 233 | tel: m, 234 | msg: msg, 235 | attachment: a, 236 | group: &groupMessage{ 237 | id: g.ID, 238 | typ: signalservice.GroupContext_DELIVER, 239 | }, 240 | } 241 | ts, err = sendMessage(omsg) 242 | if err != nil { 243 | log.Errorln("[textsecure] sendGroupHelper", err, m) 244 | return 0, err 245 | } else { 246 | log.Debugln("[textsecure] sendGroupHelper message to group sent", m) 247 | 248 | } 249 | } 250 | } 251 | return ts, nil 252 | } 253 | 254 | // SendGroupMessage sends a text message to a given group. 255 | func SendGroupMessage(hexid string, msg string) (uint64, error) { 256 | return sendGroupHelper(hexid, msg, nil) 257 | } 258 | 259 | // SendGroupAttachment sends an attachment to a given group. 260 | func SendGroupAttachment(hexid string, msg string, r io.Reader) (uint64, error) { 261 | ct, r := MIMETypeFromReader(r) 262 | a, err := uploadAttachment(r, ct) 263 | if err != nil { 264 | return 0, err 265 | } 266 | return sendGroupHelper(hexid, msg, a) 267 | } 268 | 269 | func newGroupID() []byte { 270 | id := make([]byte, 16) 271 | randBytes(id) 272 | return id 273 | } 274 | 275 | func newPartlyGroup(id []byte) (*Group, error) { 276 | hexid := idToHex(id) 277 | groups[hexid] = &Group{ 278 | ID: id, 279 | Hexid: hexid, 280 | Name: "", 281 | Members: nil, 282 | Avatar: nil, 283 | } 284 | err := saveGroup(hexid) 285 | if err != nil { 286 | return nil, err 287 | } 288 | return groups[hexid], nil 289 | } 290 | 291 | func changeGroup(hexid, name string, members []string) (*Group, error) { 292 | g, ok := groups[hexid] 293 | if !ok { 294 | return nil, UnknownGroupIDError{hexid} 295 | } 296 | 297 | g.Name = name 298 | g.Members = append(members, config.Tel) 299 | saveGroup(hexid) 300 | 301 | return g, nil 302 | } 303 | 304 | func sendUpdate(g *Group) error { 305 | for _, m := range g.Members { 306 | if m != config.Tel { 307 | omsg := &outgoingMessage{ 308 | tel: m, 309 | group: &groupMessage{ 310 | id: g.ID, 311 | name: g.Name, 312 | members: g.Members, 313 | typ: signalservice.GroupContext_UPDATE, 314 | }, 315 | } 316 | _, err := sendMessage(omsg) 317 | if err != nil { 318 | return err 319 | } 320 | } 321 | } 322 | return nil 323 | } 324 | func newGroup(name string, members []string) (*Group, error) { 325 | id := newGroupID() 326 | hexid := idToHex(id) 327 | groups[hexid] = &Group{ 328 | ID: id, 329 | Hexid: hexid, 330 | Name: name, 331 | Members: append(members, config.Tel), 332 | } 333 | err := saveGroup(hexid) 334 | if err != nil { 335 | return nil, err 336 | } 337 | return groups[hexid], nil 338 | } 339 | func RequestGroupInfo(g *Group) error { 340 | log.Debugln("[textsecure] request group update", g.Hexid) 341 | for _, m := range g.Members { 342 | if m != config.Tel { 343 | log.Debugln(m) 344 | omsg := &outgoingMessage{ 345 | tel: m, 346 | group: &groupMessage{ 347 | id: g.ID, 348 | typ: signalservice.GroupContext_REQUEST_INFO, 349 | }, 350 | } 351 | _, err := sendMessage(omsg) 352 | if err != nil { 353 | return err 354 | } 355 | } 356 | } 357 | if len(g.Members) == 0 { 358 | omsg := &outgoingMessage{ 359 | tel: config.Tel, 360 | group: &groupMessage{ 361 | id: g.ID, 362 | typ: signalservice.GroupContext_REQUEST_INFO, 363 | }, 364 | } 365 | _, err := sendMessage(omsg) 366 | if err != nil { 367 | return err 368 | } 369 | } 370 | return nil 371 | } 372 | 373 | // NewGroup creates a group and notifies its members. 374 | // Our phone number is automatically added to members. 375 | func NewGroup(name string, members []string) (*Group, error) { 376 | g, err := newGroup(name, members) 377 | if err != nil { 378 | return nil, err 379 | } 380 | return g, sendUpdate(g) 381 | } 382 | 383 | // UpdateGroup updates the group name and/or membership. 384 | // Our phone number is automatically added to members. 385 | func UpdateGroup(hexid, name string, members []string) (*Group, error) { 386 | g, err := changeGroup(hexid, name, members) 387 | if err != nil { 388 | return nil, err 389 | } 390 | return g, sendUpdate(g) 391 | } 392 | 393 | func removeGroup(id []byte) error { 394 | hexid := idToHex(id) 395 | err := os.Remove(idToPath(hexid)) 396 | if err != nil { 397 | return err 398 | } 399 | return nil 400 | } 401 | func GetGroupById(hexId string) (*Group, error) { 402 | g, ok := groups[hexId] 403 | if !ok { 404 | return nil, UnknownGroupIDError{hexId} 405 | } 406 | return g, nil 407 | } 408 | 409 | // LeaveGroup sends a group quit message to the other members of the given group. 410 | func LeaveGroup(hexid string) error { 411 | g, ok := groups[hexid] 412 | if !ok { 413 | return UnknownGroupIDError{hexid} 414 | } 415 | 416 | for _, m := range g.Members { 417 | if m != config.Tel { 418 | omsg := &outgoingMessage{ 419 | tel: m, 420 | group: &groupMessage{ 421 | id: g.ID, 422 | typ: signalservice.GroupContext_QUIT, 423 | }, 424 | } 425 | _, err := sendMessage(omsg) 426 | if err != nil { 427 | return err 428 | } 429 | } 430 | } 431 | removeGroup(g.ID) 432 | return nil 433 | } 434 | -------------------------------------------------------------------------------- /link.go: -------------------------------------------------------------------------------- 1 | package textsecure 2 | 3 | import ( 4 | "time" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var code2 string 10 | 11 | func AddNewLinkedDevice(uuid string, publicKey string) error { 12 | log.Printf("AddNewLinkedDevice") 13 | if code2 == "" { 14 | code, err := getNewDeviceVerificationCode() 15 | if err != nil { 16 | return err 17 | } 18 | code2 = code 19 | } 20 | // log.Printf("code: " + code2) 21 | // log.Printf("uuid: " + uuid) 22 | // log.Printf("publicKey: " + publicKey) 23 | 24 | err := addNewDevice(uuid, publicKey, code2) 25 | if err != nil { 26 | log.Errorf(err.Error()) 27 | return err 28 | } 29 | timer := time.NewTimer(10 * time.Second) 30 | go func() { 31 | <-timer.C 32 | code2 = "" 33 | }() 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /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/nanu-c/textsecure/axolotl" 12 | "github.com/nanu-c/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/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | protoc --go_out=. SignalService.proto Provisioning.proto WebSocketResources.proto 4 | -------------------------------------------------------------------------------- /protobuf/Provisioning.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: Provisioning.proto 3 | 4 | package signalservice 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | // Reference imports to suppress errors if they are not otherwise used. 11 | var _ = proto.Marshal 12 | var _ = fmt.Errorf 13 | var _ = math.Inf 14 | 15 | // This is a compile-time assertion to ensure that this generated file 16 | // is compatible with the proto package it is being compiled against. 17 | // A compilation error at this line likely means your copy of the 18 | // proto package needs to be updated. 19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 20 | 21 | type ProvisioningVersion int32 22 | 23 | const ( 24 | ProvisioningVersion_INITIAL ProvisioningVersion = 0 25 | ProvisioningVersion_TABLET_SUPPORT ProvisioningVersion = 1 26 | ProvisioningVersion_CURRENT ProvisioningVersion = 1 27 | ) 28 | 29 | var ProvisioningVersion_name = map[int32]string{ 30 | 0: "INITIAL", 31 | 1: "TABLET_SUPPORT", 32 | // Duplicate value: 1: "CURRENT", 33 | } 34 | var ProvisioningVersion_value = map[string]int32{ 35 | "INITIAL": 0, 36 | "TABLET_SUPPORT": 1, 37 | "CURRENT": 1, 38 | } 39 | 40 | func (x ProvisioningVersion) Enum() *ProvisioningVersion { 41 | p := new(ProvisioningVersion) 42 | *p = x 43 | return p 44 | } 45 | func (x ProvisioningVersion) String() string { 46 | return proto.EnumName(ProvisioningVersion_name, int32(x)) 47 | } 48 | func (x *ProvisioningVersion) UnmarshalJSON(data []byte) error { 49 | value, err := proto.UnmarshalJSONEnum(ProvisioningVersion_value, data, "ProvisioningVersion") 50 | if err != nil { 51 | return err 52 | } 53 | *x = ProvisioningVersion(value) 54 | return nil 55 | } 56 | func (ProvisioningVersion) EnumDescriptor() ([]byte, []int) { 57 | return fileDescriptor_Provisioning_6ab66841a1b13740, []int{0} 58 | } 59 | 60 | type ProvisionEnvelope struct { 61 | PublicKey []byte `protobuf:"bytes,1,opt,name=publicKey" json:"publicKey,omitempty"` 62 | Body []byte `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"` 63 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 64 | XXX_unrecognized []byte `json:"-"` 65 | XXX_sizecache int32 `json:"-"` 66 | } 67 | 68 | func (m *ProvisionEnvelope) Reset() { *m = ProvisionEnvelope{} } 69 | func (m *ProvisionEnvelope) String() string { return proto.CompactTextString(m) } 70 | func (*ProvisionEnvelope) ProtoMessage() {} 71 | func (*ProvisionEnvelope) Descriptor() ([]byte, []int) { 72 | return fileDescriptor_Provisioning_6ab66841a1b13740, []int{0} 73 | } 74 | func (m *ProvisionEnvelope) XXX_Unmarshal(b []byte) error { 75 | return xxx_messageInfo_ProvisionEnvelope.Unmarshal(m, b) 76 | } 77 | func (m *ProvisionEnvelope) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 78 | return xxx_messageInfo_ProvisionEnvelope.Marshal(b, m, deterministic) 79 | } 80 | func (dst *ProvisionEnvelope) XXX_Merge(src proto.Message) { 81 | xxx_messageInfo_ProvisionEnvelope.Merge(dst, src) 82 | } 83 | func (m *ProvisionEnvelope) XXX_Size() int { 84 | return xxx_messageInfo_ProvisionEnvelope.Size(m) 85 | } 86 | func (m *ProvisionEnvelope) XXX_DiscardUnknown() { 87 | xxx_messageInfo_ProvisionEnvelope.DiscardUnknown(m) 88 | } 89 | 90 | var xxx_messageInfo_ProvisionEnvelope proto.InternalMessageInfo 91 | 92 | func (m *ProvisionEnvelope) GetPublicKey() []byte { 93 | if m != nil { 94 | return m.PublicKey 95 | } 96 | return nil 97 | } 98 | 99 | func (m *ProvisionEnvelope) GetBody() []byte { 100 | if m != nil { 101 | return m.Body 102 | } 103 | return nil 104 | } 105 | 106 | type ProvisionMessage struct { 107 | IdentityKeyPublic []byte `protobuf:"bytes,1,opt,name=identityKeyPublic" json:"identityKeyPublic,omitempty"` 108 | IdentityKeyPrivate []byte `protobuf:"bytes,2,opt,name=identityKeyPrivate" json:"identityKeyPrivate,omitempty"` 109 | Number *string `protobuf:"bytes,3,opt,name=number" json:"number,omitempty"` 110 | Uuid *string `protobuf:"bytes,8,opt,name=uuid" json:"uuid,omitempty"` 111 | ProvisioningCode *string `protobuf:"bytes,4,opt,name=provisioningCode" json:"provisioningCode,omitempty"` 112 | UserAgent *string `protobuf:"bytes,5,opt,name=userAgent" json:"userAgent,omitempty"` 113 | ProfileKey []byte `protobuf:"bytes,6,opt,name=profileKey" json:"profileKey,omitempty"` 114 | ReadReceipts *bool `protobuf:"varint,7,opt,name=readReceipts" json:"readReceipts,omitempty"` 115 | ProvisioningVersion *uint32 `protobuf:"varint,9,opt,name=provisioningVersion" json:"provisioningVersion,omitempty"` 116 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 117 | XXX_unrecognized []byte `json:"-"` 118 | XXX_sizecache int32 `json:"-"` 119 | } 120 | 121 | func (m *ProvisionMessage) Reset() { *m = ProvisionMessage{} } 122 | func (m *ProvisionMessage) String() string { return proto.CompactTextString(m) } 123 | func (*ProvisionMessage) ProtoMessage() {} 124 | func (*ProvisionMessage) Descriptor() ([]byte, []int) { 125 | return fileDescriptor_Provisioning_6ab66841a1b13740, []int{1} 126 | } 127 | func (m *ProvisionMessage) XXX_Unmarshal(b []byte) error { 128 | return xxx_messageInfo_ProvisionMessage.Unmarshal(m, b) 129 | } 130 | func (m *ProvisionMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 131 | return xxx_messageInfo_ProvisionMessage.Marshal(b, m, deterministic) 132 | } 133 | func (dst *ProvisionMessage) XXX_Merge(src proto.Message) { 134 | xxx_messageInfo_ProvisionMessage.Merge(dst, src) 135 | } 136 | func (m *ProvisionMessage) XXX_Size() int { 137 | return xxx_messageInfo_ProvisionMessage.Size(m) 138 | } 139 | func (m *ProvisionMessage) XXX_DiscardUnknown() { 140 | xxx_messageInfo_ProvisionMessage.DiscardUnknown(m) 141 | } 142 | 143 | var xxx_messageInfo_ProvisionMessage proto.InternalMessageInfo 144 | 145 | func (m *ProvisionMessage) GetIdentityKeyPublic() []byte { 146 | if m != nil { 147 | return m.IdentityKeyPublic 148 | } 149 | return nil 150 | } 151 | 152 | func (m *ProvisionMessage) GetIdentityKeyPrivate() []byte { 153 | if m != nil { 154 | return m.IdentityKeyPrivate 155 | } 156 | return nil 157 | } 158 | 159 | func (m *ProvisionMessage) GetNumber() string { 160 | if m != nil && m.Number != nil { 161 | return *m.Number 162 | } 163 | return "" 164 | } 165 | 166 | func (m *ProvisionMessage) GetUuid() string { 167 | if m != nil && m.Uuid != nil { 168 | return *m.Uuid 169 | } 170 | return "" 171 | } 172 | 173 | func (m *ProvisionMessage) GetProvisioningCode() string { 174 | if m != nil && m.ProvisioningCode != nil { 175 | return *m.ProvisioningCode 176 | } 177 | return "" 178 | } 179 | 180 | func (m *ProvisionMessage) GetUserAgent() string { 181 | if m != nil && m.UserAgent != nil { 182 | return *m.UserAgent 183 | } 184 | return "" 185 | } 186 | 187 | func (m *ProvisionMessage) GetProfileKey() []byte { 188 | if m != nil { 189 | return m.ProfileKey 190 | } 191 | return nil 192 | } 193 | 194 | func (m *ProvisionMessage) GetReadReceipts() bool { 195 | if m != nil && m.ReadReceipts != nil { 196 | return *m.ReadReceipts 197 | } 198 | return false 199 | } 200 | 201 | func (m *ProvisionMessage) GetProvisioningVersion() uint32 { 202 | if m != nil && m.ProvisioningVersion != nil { 203 | return *m.ProvisioningVersion 204 | } 205 | return 0 206 | } 207 | 208 | func init() { 209 | proto.RegisterType((*ProvisionEnvelope)(nil), "signalservice.ProvisionEnvelope") 210 | proto.RegisterType((*ProvisionMessage)(nil), "signalservice.ProvisionMessage") 211 | proto.RegisterEnum("signalservice.ProvisioningVersion", ProvisioningVersion_name, ProvisioningVersion_value) 212 | } 213 | 214 | func init() { proto.RegisterFile("Provisioning.proto", fileDescriptor_Provisioning_6ab66841a1b13740) } 215 | 216 | var fileDescriptor_Provisioning_6ab66841a1b13740 = []byte{ 217 | // 361 bytes of a gzipped FileDescriptorProto 218 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0x61, 0x6b, 0xdb, 0x30, 219 | 0x10, 0x86, 0xa7, 0xac, 0x6b, 0x9b, 0x5b, 0x3b, 0xdc, 0x2b, 0x0c, 0x31, 0xc6, 0x30, 0xf9, 0x64, 220 | 0xca, 0x30, 0xfb, 0x0b, 0x49, 0x67, 0x46, 0x68, 0xd7, 0x19, 0xcd, 0xdd, 0xd7, 0xe1, 0xc4, 0x37, 221 | 0x57, 0xe0, 0x48, 0x42, 0x92, 0x3d, 0xfc, 0x5f, 0xf7, 0x63, 0x86, 0x45, 0x48, 0x1c, 0x92, 0x6f, 222 | 0xa7, 0xe7, 0x3d, 0xde, 0x3b, 0x1d, 0x2f, 0x60, 0x6e, 0x75, 0x27, 0x9d, 0xd4, 0x4a, 0xaa, 0x3a, 223 | 0x35, 0x56, 0x7b, 0x8d, 0xd7, 0x4e, 0xd6, 0xaa, 0x6c, 0x1c, 0xd9, 0x4e, 0xae, 0x69, 0x96, 0xc1, 224 | 0xcd, 0xae, 0x29, 0x53, 0x1d, 0x35, 0xda, 0x10, 0x7e, 0x84, 0xa9, 0x69, 0x57, 0x8d, 0x5c, 0x3f, 225 | 0x50, 0xcf, 0x59, 0xcc, 0x92, 0x2b, 0xb1, 0x07, 0x88, 0x70, 0xb6, 0xd2, 0x55, 0xcf, 0x27, 0x41, 226 | 0x08, 0xf5, 0xec, 0xdf, 0x04, 0xa2, 0x9d, 0xcf, 0x77, 0x72, 0xae, 0xac, 0x09, 0x3f, 0xc3, 0x8d, 227 | 0xac, 0x48, 0x79, 0xe9, 0xfb, 0x07, 0xea, 0xf3, 0x60, 0xb0, 0xb5, 0x3b, 0x16, 0x30, 0x05, 0x1c, 228 | 0x43, 0x2b, 0xbb, 0xd2, 0xd3, 0x76, 0xc8, 0x09, 0x05, 0xdf, 0xc3, 0xb9, 0x6a, 0x37, 0x2b, 0xb2, 229 | 0xfc, 0x75, 0xcc, 0x92, 0xa9, 0xd8, 0xbe, 0x86, 0xf5, 0xda, 0x56, 0x56, 0xfc, 0x32, 0xd0, 0x50, 230 | 0xe3, 0x1d, 0x44, 0x66, 0x74, 0x8a, 0x7b, 0x5d, 0x11, 0x3f, 0x0b, 0xfa, 0x11, 0x1f, 0x3e, 0xdf, 231 | 0x3a, 0xb2, 0xf3, 0x9a, 0x94, 0xe7, 0x6f, 0x42, 0xd3, 0x1e, 0xe0, 0x27, 0x00, 0x63, 0xf5, 0x1f, 232 | 0xd9, 0xd0, 0x70, 0x9b, 0xf3, 0xb0, 0xdd, 0x88, 0xe0, 0x0c, 0xae, 0x2c, 0x95, 0x95, 0xa0, 0x35, 233 | 0x49, 0xe3, 0x1d, 0xbf, 0x88, 0x59, 0x72, 0x29, 0x0e, 0x18, 0x7e, 0x81, 0xdb, 0xf1, 0xd4, 0x5f, 234 | 0x64, 0x87, 0x8a, 0x4f, 0x63, 0x96, 0x5c, 0x8b, 0x53, 0xd2, 0xdd, 0x37, 0xb8, 0xcd, 0x8f, 0x31, 235 | 0xbe, 0x85, 0x8b, 0xe5, 0xd3, 0xb2, 0x58, 0xce, 0x1f, 0xa3, 0x57, 0x88, 0xf0, 0xae, 0x98, 0x2f, 236 | 0x1e, 0xb3, 0xe2, 0xf7, 0xcf, 0xe7, 0x3c, 0xff, 0x21, 0x8a, 0x88, 0x0d, 0x0d, 0xf7, 0xcf, 0x42, 237 | 0x64, 0x4f, 0x45, 0xc4, 0x3e, 0x4c, 0x22, 0xb6, 0xf8, 0x0a, 0xa9, 0xb6, 0x75, 0xfa, 0xf7, 0x45, 238 | 0x3a, 0x43, 0xd6, 0xf5, 0xce, 0xd3, 0xc6, 0xa5, 0x07, 0x91, 0x48, 0xa5, 0xf2, 0x64, 0x55, 0xd9, 239 | 0xa4, 0xa6, 0x75, 0x2f, 0x8b, 0x83, 0x0c, 0xe5, 0x43, 0x84, 0xdc, 0xff, 0x00, 0x00, 0x00, 0xff, 240 | 0xff, 0xdf, 0x99, 0x18, 0x14, 0x58, 0x02, 0x00, 0x00, 241 | } 242 | -------------------------------------------------------------------------------- /protobuf/Provisioning.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.push"; 11 | option java_outer_classname = "ProvisioningProtos"; 12 | 13 | message ProvisionEnvelope { 14 | optional bytes publicKey = 1; 15 | optional bytes body = 2; // Encrypted ProvisionMessage 16 | } 17 | 18 | message ProvisionMessage { 19 | optional bytes identityKeyPublic = 1; 20 | optional bytes identityKeyPrivate = 2; 21 | optional string number = 3; 22 | optional string uuid = 8; 23 | optional string provisioningCode = 4; 24 | optional string userAgent = 5; 25 | optional bytes profileKey = 6; 26 | optional bool readReceipts = 7; 27 | optional uint32 provisioningVersion = 9; 28 | } 29 | 30 | enum ProvisioningVersion { 31 | option allow_alias = true; 32 | 33 | INITIAL = 0; 34 | TABLET_SUPPORT = 1; 35 | CURRENT = 1; 36 | } 37 | -------------------------------------------------------------------------------- /protobuf/SignalService.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.push"; 11 | option java_outer_classname = "SignalServiceProtos"; 12 | 13 | message Envelope { 14 | enum Type { 15 | UNKNOWN = 0; 16 | CIPHERTEXT = 1; 17 | KEY_EXCHANGE = 2; 18 | PREKEY_BUNDLE = 3; 19 | RECEIPT = 5; 20 | UNIDENTIFIED_SENDER = 6; 21 | } 22 | 23 | optional Type type = 1; 24 | optional string sourceE164 = 2; 25 | optional string sourceUuid = 11; 26 | optional uint32 sourceDevice = 7; 27 | optional string relay = 3; 28 | optional uint64 timestamp = 5; 29 | optional bytes legacyMessage = 6; // Contains an encrypted DataMessage 30 | optional bytes content = 8; // Contains an encrypted Content 31 | optional string serverGuid = 9; 32 | optional uint64 serverTimestamp = 10; 33 | } 34 | 35 | message Content { 36 | optional DataMessage dataMessage = 1; 37 | optional SyncMessage syncMessage = 2; 38 | optional CallMessage callMessage = 3; 39 | optional NullMessage nullMessage = 4; 40 | optional ReceiptMessage receiptMessage = 5; 41 | optional TypingMessage typingMessage = 6; 42 | } 43 | 44 | message CallMessage { 45 | message Offer { 46 | optional uint64 id = 1; 47 | optional string description = 2; 48 | } 49 | 50 | message Answer { 51 | optional uint64 id = 1; 52 | optional string description = 2; 53 | } 54 | 55 | message IceUpdate { 56 | optional uint64 id = 1; 57 | optional string sdpMid = 2; 58 | optional uint32 sdpMLineIndex = 3; 59 | optional string sdp = 4; 60 | } 61 | 62 | message Busy { 63 | optional uint64 id = 1; 64 | } 65 | 66 | message Hangup { 67 | optional uint64 id = 1; 68 | } 69 | 70 | 71 | optional Offer offer = 1; 72 | optional Answer answer = 2; 73 | repeated IceUpdate iceUpdate = 3; 74 | optional Hangup hangup = 4; 75 | optional Busy busy = 5; 76 | } 77 | 78 | message DataMessage { 79 | enum Flags { 80 | END_SESSION = 1; 81 | EXPIRATION_TIMER_UPDATE = 2; 82 | PROFILE_KEY_UPDATE = 4; 83 | } 84 | 85 | message Quote { 86 | message QuotedAttachment { 87 | optional string contentType = 1; 88 | optional string fileName = 2; 89 | optional AttachmentPointer thumbnail = 3; 90 | } 91 | 92 | optional uint64 id = 1; 93 | optional string authorE164 = 2; 94 | optional string authorUuid = 5; 95 | optional string text = 3; 96 | repeated QuotedAttachment attachments = 4; 97 | } 98 | 99 | message Contact { 100 | message Name { 101 | optional string givenName = 1; 102 | optional string familyName = 2; 103 | optional string prefix = 3; 104 | optional string suffix = 4; 105 | optional string middleName = 5; 106 | optional string displayName = 6; 107 | } 108 | 109 | message Phone { 110 | enum Type { 111 | HOME = 1; 112 | MOBILE = 2; 113 | WORK = 3; 114 | CUSTOM = 4; 115 | } 116 | 117 | optional string value = 1; 118 | optional Type type = 2; 119 | optional string label = 3; 120 | } 121 | 122 | message Email { 123 | enum Type { 124 | HOME = 1; 125 | MOBILE = 2; 126 | WORK = 3; 127 | CUSTOM = 4; 128 | } 129 | 130 | optional string value = 1; 131 | optional Type type = 2; 132 | optional string label = 3; 133 | } 134 | 135 | message PostalAddress { 136 | enum Type { 137 | HOME = 1; 138 | WORK = 2; 139 | CUSTOM = 3; 140 | } 141 | 142 | optional Type type = 1; 143 | optional string label = 2; 144 | optional string street = 3; 145 | optional string pobox = 4; 146 | optional string neighborhood = 5; 147 | optional string city = 6; 148 | optional string region = 7; 149 | optional string postcode = 8; 150 | optional string country = 9; 151 | } 152 | 153 | message Avatar { 154 | optional AttachmentPointer avatar = 1; 155 | optional bool isProfile = 2; 156 | } 157 | 158 | optional Name name = 1; 159 | repeated Phone number = 3; 160 | repeated Email email = 4; 161 | repeated PostalAddress address = 5; 162 | optional Avatar avatar = 6; 163 | optional string organization = 7; 164 | } 165 | 166 | message Preview { 167 | optional string url = 1; 168 | optional string title = 2; 169 | optional AttachmentPointer image = 3; 170 | } 171 | 172 | message Sticker { 173 | optional bytes packId = 1; 174 | optional bytes packKey = 2; 175 | optional uint32 stickerId = 3; 176 | optional AttachmentPointer data = 4; 177 | } 178 | 179 | enum ProtocolVersion { 180 | option allow_alias = true; 181 | 182 | INITIAL = 0; 183 | MESSAGE_TIMERS = 1; 184 | VIEW_ONCE = 2; 185 | VIEW_ONCE_VIDEO = 3; 186 | CURRENT = 3; 187 | } 188 | 189 | optional string body = 1; 190 | repeated AttachmentPointer attachments = 2; 191 | optional GroupContext group = 3; 192 | optional uint32 flags = 4; 193 | optional uint32 expireTimer = 5; 194 | optional bytes profileKey = 6; 195 | optional uint64 timestamp = 7; 196 | optional Quote quote = 8; 197 | repeated Contact contact = 9; 198 | repeated Preview preview = 10; 199 | optional Sticker sticker = 11; 200 | optional uint32 requiredProtocolVersion = 12; 201 | optional bool isViewOnce = 14; 202 | } 203 | 204 | message NullMessage { 205 | optional bytes padding = 1; 206 | } 207 | 208 | message ReceiptMessage { 209 | enum Type { 210 | DELIVERY = 0; 211 | READ = 1; 212 | } 213 | 214 | optional Type type = 1; 215 | repeated uint64 timestamp = 2; 216 | } 217 | 218 | message TypingMessage { 219 | enum Action { 220 | STARTED = 0; 221 | STOPPED = 1; 222 | } 223 | 224 | optional uint64 timestamp = 1; 225 | optional Action action = 2; 226 | optional bytes groupId = 3; 227 | } 228 | 229 | message Verified { 230 | enum State { 231 | DEFAULT = 0; 232 | VERIFIED = 1; 233 | UNVERIFIED = 2; 234 | } 235 | 236 | optional string destinationE164 = 1; 237 | optional string destinationUuid = 5; 238 | optional bytes identityKey = 2; 239 | optional State state = 3; 240 | optional bytes nullMessage = 4; 241 | } 242 | 243 | message SyncMessage { 244 | message Sent { 245 | message UnidentifiedDeliveryStatus { 246 | optional string destinationE164 = 1; 247 | optional string destinationUuid = 3; 248 | optional bool unidentified = 2; 249 | } 250 | 251 | optional string destinationE164 = 1; 252 | optional string destinationUuid = 7; 253 | optional uint64 timestamp = 2; 254 | optional DataMessage message = 3; 255 | optional uint64 expirationStartTimestamp = 4; 256 | repeated UnidentifiedDeliveryStatus unidentifiedStatus = 5; 257 | optional bool isRecipientUpdate = 6 [default = false]; 258 | } 259 | 260 | message Contacts { 261 | optional AttachmentPointer blob = 1; 262 | optional bool complete = 2 [default = false]; 263 | } 264 | 265 | message Groups { 266 | optional AttachmentPointer blob = 1; 267 | } 268 | 269 | message Blocked { 270 | repeated string numbers = 1; 271 | repeated string uuids = 3; 272 | repeated bytes groupIds = 2; 273 | } 274 | 275 | message Request { 276 | enum Type { 277 | UNKNOWN = 0; 278 | CONTACTS = 1; 279 | GROUPS = 2; 280 | BLOCKED = 3; 281 | CONFIGURATION = 4; 282 | } 283 | 284 | optional Type type = 1; 285 | } 286 | 287 | message Read { 288 | optional string senderE164 = 1; 289 | optional string senderUuid = 3; 290 | optional uint64 timestamp = 2; 291 | } 292 | 293 | message Configuration { 294 | optional bool readReceipts = 1; 295 | optional bool unidentifiedDeliveryIndicators = 2; 296 | optional bool typingIndicators = 3; 297 | optional bool linkPreviews = 4; 298 | optional uint32 provisioningVersion = 5; 299 | } 300 | 301 | message StickerPackOperation { 302 | enum Type { 303 | INSTALL = 0; 304 | REMOVE = 1; 305 | } 306 | 307 | optional bytes packId = 1; 308 | optional bytes packKey = 2; 309 | optional Type type = 3; 310 | } 311 | 312 | message ViewOnceOpen { 313 | optional string senderE164 = 1; 314 | optional string senderUuid = 3; 315 | optional uint64 timestamp = 2; 316 | } 317 | 318 | 319 | message FetchLatest { 320 | enum Type { 321 | UNKNOWN = 0; 322 | LOCAL_PROFILE = 1; 323 | STORAGE_MANIFEST = 2; 324 | } 325 | 326 | optional Type type = 1; 327 | } 328 | 329 | 330 | optional Sent sent = 1; 331 | optional Contacts contacts = 2; 332 | optional Groups groups = 3; 333 | optional Request request = 4; 334 | repeated Read read = 5; 335 | optional Blocked blocked = 6; 336 | optional Verified verified = 7; 337 | optional Configuration configuration = 9; 338 | optional bytes padding = 8; 339 | repeated StickerPackOperation stickerPackOperation = 10; 340 | optional ViewOnceOpen viewOnceOpen = 11; 341 | optional FetchLatest fetchLatest = 12; 342 | } 343 | 344 | message AttachmentPointer { 345 | enum Flags { 346 | VOICE_MESSAGE = 1; 347 | } 348 | 349 | optional fixed64 id = 1; 350 | optional string contentType = 2; 351 | optional bytes key = 3; 352 | optional uint32 size = 4; 353 | optional bytes thumbnail = 5; 354 | optional bytes digest = 6; 355 | optional string fileName = 7; 356 | optional uint32 flags = 8; 357 | optional uint32 width = 9; 358 | optional uint32 height = 10; 359 | optional string caption = 11; 360 | optional string blurHash = 12; 361 | } 362 | 363 | message GroupContext { 364 | enum Type { 365 | UNKNOWN = 0; 366 | UPDATE = 1; 367 | DELIVER = 2; 368 | QUIT = 3; 369 | REQUEST_INFO = 4; 370 | } 371 | 372 | message Member { 373 | optional string uuid = 1; 374 | optional string e164 = 2; 375 | } 376 | 377 | optional bytes id = 1; 378 | optional Type type = 2; 379 | optional string name = 3; 380 | repeated string membersE164 = 4; 381 | repeated Member members = 6; 382 | optional AttachmentPointer avatar = 5; 383 | } 384 | 385 | message ContactDetails { 386 | message Avatar { 387 | optional string contentType = 1; 388 | optional uint32 length = 2; 389 | } 390 | 391 | optional string number = 1; 392 | optional string uuid = 9; 393 | optional string name = 2; 394 | optional Avatar avatar = 3; 395 | optional string color = 4; 396 | optional Verified verified = 5; 397 | optional bytes profileKey = 6; 398 | optional bool blocked = 7; 399 | optional uint32 expireTimer = 8; 400 | } 401 | 402 | message GroupDetails { 403 | message Avatar { 404 | optional string contentType = 1; 405 | optional uint32 length = 2; 406 | } 407 | 408 | message Member { 409 | optional string uuid = 1; 410 | optional string e164 = 2; 411 | } 412 | 413 | optional bytes id = 1; 414 | optional string name = 2; 415 | repeated string membersE164 = 3; 416 | repeated Member members = 9; 417 | optional Avatar avatar = 4; 418 | optional bool active = 5 [default = true]; 419 | optional uint32 expireTimer = 6; 420 | optional string color = 7; 421 | optional bool blocked = 8; 422 | } 423 | -------------------------------------------------------------------------------- /protobuf/StickerResources.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: StickerResources.proto 3 | 4 | package signalservice 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | // Reference imports to suppress errors if they are not otherwise used. 11 | var _ = proto.Marshal 12 | var _ = fmt.Errorf 13 | var _ = math.Inf 14 | 15 | type Pack struct { 16 | Title *string `protobuf:"bytes,1,opt,name=title" json:"title,omitempty"` 17 | Author *string `protobuf:"bytes,2,opt,name=author" json:"author,omitempty"` 18 | Cover *Pack_Sticker `protobuf:"bytes,3,opt,name=cover" json:"cover,omitempty"` 19 | Stickers []*Pack_Sticker `protobuf:"bytes,4,rep,name=stickers" json:"stickers,omitempty"` 20 | XXX_unrecognized []byte `json:"-"` 21 | } 22 | 23 | func (m *Pack) Reset() { *m = Pack{} } 24 | func (m *Pack) String() string { return proto.CompactTextString(m) } 25 | func (*Pack) ProtoMessage() {} 26 | func (*Pack) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } 27 | 28 | func (m *Pack) GetTitle() string { 29 | if m != nil && m.Title != nil { 30 | return *m.Title 31 | } 32 | return "" 33 | } 34 | 35 | func (m *Pack) GetAuthor() string { 36 | if m != nil && m.Author != nil { 37 | return *m.Author 38 | } 39 | return "" 40 | } 41 | 42 | func (m *Pack) GetCover() *Pack_Sticker { 43 | if m != nil { 44 | return m.Cover 45 | } 46 | return nil 47 | } 48 | 49 | func (m *Pack) GetStickers() []*Pack_Sticker { 50 | if m != nil { 51 | return m.Stickers 52 | } 53 | return nil 54 | } 55 | 56 | type Pack_Sticker struct { 57 | Id *uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 58 | Emoji *string `protobuf:"bytes,2,opt,name=emoji" json:"emoji,omitempty"` 59 | XXX_unrecognized []byte `json:"-"` 60 | } 61 | 62 | func (m *Pack_Sticker) Reset() { *m = Pack_Sticker{} } 63 | func (m *Pack_Sticker) String() string { return proto.CompactTextString(m) } 64 | func (*Pack_Sticker) ProtoMessage() {} 65 | func (*Pack_Sticker) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0, 0} } 66 | 67 | func (m *Pack_Sticker) GetId() uint32 { 68 | if m != nil && m.Id != nil { 69 | return *m.Id 70 | } 71 | return 0 72 | } 73 | 74 | func (m *Pack_Sticker) GetEmoji() string { 75 | if m != nil && m.Emoji != nil { 76 | return *m.Emoji 77 | } 78 | return "" 79 | } 80 | 81 | func init() { 82 | proto.RegisterType((*Pack)(nil), "signalservice.Pack") 83 | proto.RegisterType((*Pack_Sticker)(nil), "signalservice.Pack.Sticker") 84 | } 85 | 86 | func init() { proto.RegisterFile("StickerResources.proto", fileDescriptor2) } 87 | 88 | var fileDescriptor2 = []byte{ 89 | // 220 bytes of a gzipped FileDescriptorProto 90 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xcd, 0x4e, 0xc3, 0x30, 91 | 0x10, 0x84, 0xe5, 0xb4, 0xe5, 0x67, 0x51, 0x38, 0x58, 0xa8, 0xb2, 0xe0, 0x12, 0x71, 0xea, 0xc9, 92 | 0xa8, 0x5c, 0xb8, 0xe7, 0x09, 0xaa, 0xf0, 0x04, 0x96, 0xbb, 0x6a, 0x97, 0xa6, 0x71, 0xb4, 0xeb, 93 | 0x04, 0xf1, 0x98, 0xbc, 0x11, 0x4a, 0x6c, 0x90, 0xe0, 0xc2, 0x71, 0x46, 0xb3, 0x33, 0x9f, 0x16, 94 | 0xd6, 0xaf, 0x91, 0xfc, 0x09, 0xb9, 0x41, 0x09, 0x03, 0x7b, 0x14, 0xdb, 0x73, 0x88, 0x41, 0x97, 95 | 0x42, 0x87, 0xce, 0xb5, 0x82, 0x3c, 0x92, 0xc7, 0xc7, 0x4f, 0x05, 0xcb, 0x9d, 0xf3, 0x27, 0x7d, 96 | 0x07, 0xab, 0x48, 0xb1, 0x45, 0xa3, 0x2a, 0xb5, 0xb9, 0x6e, 0x92, 0xd0, 0x6b, 0xb8, 0x70, 0x43, 97 | 0x3c, 0x06, 0x36, 0xc5, 0x6c, 0x67, 0xa5, 0xb7, 0xb0, 0xf2, 0x61, 0x44, 0x36, 0x8b, 0x4a, 0x6d, 98 | 0x6e, 0x9e, 0x1f, 0xec, 0xaf, 0x56, 0x3b, 0x35, 0xda, 0x6f, 0x80, 0x94, 0xd4, 0x2f, 0x70, 0x25, 99 | 0xc9, 0x11, 0xb3, 0xac, 0x16, 0xff, 0x5d, 0xfd, 0x84, 0xef, 0x9f, 0xe0, 0x32, 0x9b, 0xfa, 0x16, 100 | 0x0a, 0xda, 0xcf, 0x84, 0x65, 0x53, 0xd0, 0x7e, 0x82, 0xc6, 0x73, 0x78, 0xa3, 0x4c, 0x97, 0x44, 101 | 0x5d, 0xc3, 0x36, 0xf0, 0xc1, 0xbe, 0x1f, 0x49, 0x7a, 0x64, 0xf9, 0x90, 0x88, 0x67, 0xf9, 0xb3, 102 | 0x45, 0x5d, 0x44, 0xee, 0x5c, 0x6b, 0xf3, 0x4c, 0x5d, 0xe6, 0x8d, 0xdd, 0xf4, 0x25, 0xf9, 0x0a, 103 | 0x00, 0x00, 0xff, 0xff, 0xad, 0xa7, 0xbf, 0x08, 0x3f, 0x01, 0x00, 0x00, 104 | } 105 | -------------------------------------------------------------------------------- /protobuf/StickerResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.push"; 11 | option java_outer_classname = "ProvisioningProtos"; 12 | 13 | message ProvisionEnvelope { 14 | optional bytes publicKey = 1; 15 | optional bytes body = 2; // Encrypted ProvisionMessage 16 | } 17 | 18 | message ProvisionMessage { 19 | optional bytes identityKeyPublic = 1; 20 | optional bytes identityKeyPrivate = 2; 21 | optional string number = 3; 22 | optional string uuid = 8; 23 | optional string provisioningCode = 4; 24 | optional string userAgent = 5; 25 | optional bytes profileKey = 6; 26 | optional bool readReceipts = 7; 27 | optional uint32 provisioningVersion = 9; 28 | } 29 | 30 | enum ProvisioningVersion { 31 | option allow_alias = true; 32 | 33 | INITIAL = 0; 34 | TABLET_SUPPORT = 1; 35 | CURRENT = 1; 36 | } 37 | -------------------------------------------------------------------------------- /protobuf/WebSocketResources.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: WebSocketResources.proto 3 | 4 | package signalservice 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | // Reference imports to suppress errors if they are not otherwise used. 11 | var _ = proto.Marshal 12 | var _ = fmt.Errorf 13 | var _ = math.Inf 14 | 15 | // This is a compile-time assertion to ensure that this generated file 16 | // is compatible with the proto package it is being compiled against. 17 | // A compilation error at this line likely means your copy of the 18 | // proto package needs to be updated. 19 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 20 | 21 | type WebSocketMessage_Type int32 22 | 23 | const ( 24 | WebSocketMessage_UNKNOWN WebSocketMessage_Type = 0 25 | WebSocketMessage_REQUEST WebSocketMessage_Type = 1 26 | WebSocketMessage_RESPONSE WebSocketMessage_Type = 2 27 | ) 28 | 29 | var WebSocketMessage_Type_name = map[int32]string{ 30 | 0: "UNKNOWN", 31 | 1: "REQUEST", 32 | 2: "RESPONSE", 33 | } 34 | var WebSocketMessage_Type_value = map[string]int32{ 35 | "UNKNOWN": 0, 36 | "REQUEST": 1, 37 | "RESPONSE": 2, 38 | } 39 | 40 | func (x WebSocketMessage_Type) Enum() *WebSocketMessage_Type { 41 | p := new(WebSocketMessage_Type) 42 | *p = x 43 | return p 44 | } 45 | func (x WebSocketMessage_Type) String() string { 46 | return proto.EnumName(WebSocketMessage_Type_name, int32(x)) 47 | } 48 | func (x *WebSocketMessage_Type) UnmarshalJSON(data []byte) error { 49 | value, err := proto.UnmarshalJSONEnum(WebSocketMessage_Type_value, data, "WebSocketMessage_Type") 50 | if err != nil { 51 | return err 52 | } 53 | *x = WebSocketMessage_Type(value) 54 | return nil 55 | } 56 | func (WebSocketMessage_Type) EnumDescriptor() ([]byte, []int) { 57 | return fileDescriptor_WebSocketResources_39c2df86603aa621, []int{2, 0} 58 | } 59 | 60 | type WebSocketRequestMessage struct { 61 | Verb *string `protobuf:"bytes,1,opt,name=verb" json:"verb,omitempty"` 62 | Path *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"` 63 | Body []byte `protobuf:"bytes,3,opt,name=body" json:"body,omitempty"` 64 | Headers []string `protobuf:"bytes,5,rep,name=headers" json:"headers,omitempty"` 65 | Id *uint64 `protobuf:"varint,4,opt,name=id" json:"id,omitempty"` 66 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 67 | XXX_unrecognized []byte `json:"-"` 68 | XXX_sizecache int32 `json:"-"` 69 | } 70 | 71 | func (m *WebSocketRequestMessage) Reset() { *m = WebSocketRequestMessage{} } 72 | func (m *WebSocketRequestMessage) String() string { return proto.CompactTextString(m) } 73 | func (*WebSocketRequestMessage) ProtoMessage() {} 74 | func (*WebSocketRequestMessage) Descriptor() ([]byte, []int) { 75 | return fileDescriptor_WebSocketResources_39c2df86603aa621, []int{0} 76 | } 77 | func (m *WebSocketRequestMessage) XXX_Unmarshal(b []byte) error { 78 | return xxx_messageInfo_WebSocketRequestMessage.Unmarshal(m, b) 79 | } 80 | func (m *WebSocketRequestMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 81 | return xxx_messageInfo_WebSocketRequestMessage.Marshal(b, m, deterministic) 82 | } 83 | func (dst *WebSocketRequestMessage) XXX_Merge(src proto.Message) { 84 | xxx_messageInfo_WebSocketRequestMessage.Merge(dst, src) 85 | } 86 | func (m *WebSocketRequestMessage) XXX_Size() int { 87 | return xxx_messageInfo_WebSocketRequestMessage.Size(m) 88 | } 89 | func (m *WebSocketRequestMessage) XXX_DiscardUnknown() { 90 | xxx_messageInfo_WebSocketRequestMessage.DiscardUnknown(m) 91 | } 92 | 93 | var xxx_messageInfo_WebSocketRequestMessage proto.InternalMessageInfo 94 | 95 | func (m *WebSocketRequestMessage) GetVerb() string { 96 | if m != nil && m.Verb != nil { 97 | return *m.Verb 98 | } 99 | return "" 100 | } 101 | 102 | func (m *WebSocketRequestMessage) GetPath() string { 103 | if m != nil && m.Path != nil { 104 | return *m.Path 105 | } 106 | return "" 107 | } 108 | 109 | func (m *WebSocketRequestMessage) GetBody() []byte { 110 | if m != nil { 111 | return m.Body 112 | } 113 | return nil 114 | } 115 | 116 | func (m *WebSocketRequestMessage) GetHeaders() []string { 117 | if m != nil { 118 | return m.Headers 119 | } 120 | return nil 121 | } 122 | 123 | func (m *WebSocketRequestMessage) GetId() uint64 { 124 | if m != nil && m.Id != nil { 125 | return *m.Id 126 | } 127 | return 0 128 | } 129 | 130 | type WebSocketResponseMessage struct { 131 | Id *uint64 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` 132 | Status *uint32 `protobuf:"varint,2,opt,name=status" json:"status,omitempty"` 133 | Message *string `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"` 134 | Headers []string `protobuf:"bytes,5,rep,name=headers" json:"headers,omitempty"` 135 | Body []byte `protobuf:"bytes,4,opt,name=body" json:"body,omitempty"` 136 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 137 | XXX_unrecognized []byte `json:"-"` 138 | XXX_sizecache int32 `json:"-"` 139 | } 140 | 141 | func (m *WebSocketResponseMessage) Reset() { *m = WebSocketResponseMessage{} } 142 | func (m *WebSocketResponseMessage) String() string { return proto.CompactTextString(m) } 143 | func (*WebSocketResponseMessage) ProtoMessage() {} 144 | func (*WebSocketResponseMessage) Descriptor() ([]byte, []int) { 145 | return fileDescriptor_WebSocketResources_39c2df86603aa621, []int{1} 146 | } 147 | func (m *WebSocketResponseMessage) XXX_Unmarshal(b []byte) error { 148 | return xxx_messageInfo_WebSocketResponseMessage.Unmarshal(m, b) 149 | } 150 | func (m *WebSocketResponseMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 151 | return xxx_messageInfo_WebSocketResponseMessage.Marshal(b, m, deterministic) 152 | } 153 | func (dst *WebSocketResponseMessage) XXX_Merge(src proto.Message) { 154 | xxx_messageInfo_WebSocketResponseMessage.Merge(dst, src) 155 | } 156 | func (m *WebSocketResponseMessage) XXX_Size() int { 157 | return xxx_messageInfo_WebSocketResponseMessage.Size(m) 158 | } 159 | func (m *WebSocketResponseMessage) XXX_DiscardUnknown() { 160 | xxx_messageInfo_WebSocketResponseMessage.DiscardUnknown(m) 161 | } 162 | 163 | var xxx_messageInfo_WebSocketResponseMessage proto.InternalMessageInfo 164 | 165 | func (m *WebSocketResponseMessage) GetId() uint64 { 166 | if m != nil && m.Id != nil { 167 | return *m.Id 168 | } 169 | return 0 170 | } 171 | 172 | func (m *WebSocketResponseMessage) GetStatus() uint32 { 173 | if m != nil && m.Status != nil { 174 | return *m.Status 175 | } 176 | return 0 177 | } 178 | 179 | func (m *WebSocketResponseMessage) GetMessage() string { 180 | if m != nil && m.Message != nil { 181 | return *m.Message 182 | } 183 | return "" 184 | } 185 | 186 | func (m *WebSocketResponseMessage) GetHeaders() []string { 187 | if m != nil { 188 | return m.Headers 189 | } 190 | return nil 191 | } 192 | 193 | func (m *WebSocketResponseMessage) GetBody() []byte { 194 | if m != nil { 195 | return m.Body 196 | } 197 | return nil 198 | } 199 | 200 | type WebSocketMessage struct { 201 | Type *WebSocketMessage_Type `protobuf:"varint,1,opt,name=type,enum=signalservice.WebSocketMessage_Type" json:"type,omitempty"` 202 | Request *WebSocketRequestMessage `protobuf:"bytes,2,opt,name=request" json:"request,omitempty"` 203 | Response *WebSocketResponseMessage `protobuf:"bytes,3,opt,name=response" json:"response,omitempty"` 204 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 205 | XXX_unrecognized []byte `json:"-"` 206 | XXX_sizecache int32 `json:"-"` 207 | } 208 | 209 | func (m *WebSocketMessage) Reset() { *m = WebSocketMessage{} } 210 | func (m *WebSocketMessage) String() string { return proto.CompactTextString(m) } 211 | func (*WebSocketMessage) ProtoMessage() {} 212 | func (*WebSocketMessage) Descriptor() ([]byte, []int) { 213 | return fileDescriptor_WebSocketResources_39c2df86603aa621, []int{2} 214 | } 215 | func (m *WebSocketMessage) XXX_Unmarshal(b []byte) error { 216 | return xxx_messageInfo_WebSocketMessage.Unmarshal(m, b) 217 | } 218 | func (m *WebSocketMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 219 | return xxx_messageInfo_WebSocketMessage.Marshal(b, m, deterministic) 220 | } 221 | func (dst *WebSocketMessage) XXX_Merge(src proto.Message) { 222 | xxx_messageInfo_WebSocketMessage.Merge(dst, src) 223 | } 224 | func (m *WebSocketMessage) XXX_Size() int { 225 | return xxx_messageInfo_WebSocketMessage.Size(m) 226 | } 227 | func (m *WebSocketMessage) XXX_DiscardUnknown() { 228 | xxx_messageInfo_WebSocketMessage.DiscardUnknown(m) 229 | } 230 | 231 | var xxx_messageInfo_WebSocketMessage proto.InternalMessageInfo 232 | 233 | func (m *WebSocketMessage) GetType() WebSocketMessage_Type { 234 | if m != nil && m.Type != nil { 235 | return *m.Type 236 | } 237 | return WebSocketMessage_UNKNOWN 238 | } 239 | 240 | func (m *WebSocketMessage) GetRequest() *WebSocketRequestMessage { 241 | if m != nil { 242 | return m.Request 243 | } 244 | return nil 245 | } 246 | 247 | func (m *WebSocketMessage) GetResponse() *WebSocketResponseMessage { 248 | if m != nil { 249 | return m.Response 250 | } 251 | return nil 252 | } 253 | 254 | func init() { 255 | proto.RegisterType((*WebSocketRequestMessage)(nil), "signalservice.WebSocketRequestMessage") 256 | proto.RegisterType((*WebSocketResponseMessage)(nil), "signalservice.WebSocketResponseMessage") 257 | proto.RegisterType((*WebSocketMessage)(nil), "signalservice.WebSocketMessage") 258 | proto.RegisterEnum("signalservice.WebSocketMessage_Type", WebSocketMessage_Type_name, WebSocketMessage_Type_value) 259 | } 260 | 261 | func init() { 262 | proto.RegisterFile("WebSocketResources.proto", fileDescriptor_WebSocketResources_39c2df86603aa621) 263 | } 264 | 265 | var fileDescriptor_WebSocketResources_39c2df86603aa621 = []byte{ 266 | // 357 bytes of a gzipped FileDescriptorProto 267 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0x3f, 0x4f, 0x83, 0x50, 268 | 0x14, 0xc5, 0x85, 0xa2, 0x6d, 0x5f, 0xff, 0x48, 0xde, 0xa0, 0x8c, 0x84, 0x18, 0x65, 0x62, 0xa8, 269 | 0x8b, 0xa3, 0xa9, 0xc1, 0xc5, 0x48, 0xeb, 0xa3, 0x4d, 0x67, 0x28, 0x37, 0x2d, 0xb1, 0x05, 0x7c, 270 | 0xf7, 0xb5, 0x0d, 0x93, 0x83, 0x9b, 0x9f, 0xda, 0xf0, 0x28, 0x55, 0x9a, 0xd4, 0xed, 0x9e, 0x9b, 271 | 0x73, 0xb8, 0x3f, 0x4e, 0x1e, 0x31, 0x66, 0x10, 0xfa, 0xe9, 0xfc, 0x1d, 0x04, 0x03, 0x4c, 0x37, 272 | 0x7c, 0x0e, 0xe8, 0x64, 0x3c, 0x15, 0x29, 0xed, 0x61, 0xbc, 0x48, 0x82, 0x15, 0x02, 0xdf, 0xc6, 273 | 0x73, 0xb0, 0x3e, 0xc9, 0xf5, 0x1f, 0xeb, 0xc7, 0x06, 0x50, 0xbc, 0x02, 0x62, 0xb0, 0x00, 0x4a, 274 | 0x89, 0xb6, 0x05, 0x1e, 0x1a, 0x8a, 0xa9, 0xd8, 0x6d, 0x26, 0xe7, 0x62, 0x97, 0x05, 0x62, 0x69, 275 | 0xa8, 0xe5, 0xae, 0x98, 0x8b, 0x5d, 0x98, 0x46, 0xb9, 0xd1, 0x30, 0x15, 0xbb, 0xcb, 0xe4, 0x4c, 276 | 0x0d, 0xd2, 0x5c, 0x42, 0x10, 0x01, 0x47, 0xe3, 0xdc, 0x6c, 0xd8, 0x6d, 0x56, 0x49, 0xda, 0x27, 277 | 0x6a, 0x1c, 0x19, 0x9a, 0xa9, 0xd8, 0x1a, 0x53, 0xe3, 0xc8, 0xfa, 0x56, 0xea, 0xb0, 0x59, 0x9a, 278 | 0x20, 0x54, 0x08, 0xa5, 0x59, 0xa9, 0xcc, 0xf4, 0x8a, 0x5c, 0xa0, 0x08, 0xc4, 0x06, 0x25, 0x40, 279 | 0x8f, 0xed, 0x55, 0x71, 0x6e, 0x5d, 0x46, 0x24, 0x45, 0x9b, 0x55, 0xf2, 0x1f, 0x90, 0x0a, 0x5b, 280 | 0xfb, 0xc5, 0xb6, 0xbe, 0x54, 0xa2, 0x1f, 0x60, 0x2a, 0x88, 0x07, 0xa2, 0x89, 0x3c, 0x03, 0x89, 281 | 0xd1, 0x1f, 0xdc, 0x38, 0xb5, 0x02, 0x9d, 0x63, 0xbb, 0x33, 0xc9, 0x33, 0x60, 0x32, 0x41, 0x1f, 282 | 0x49, 0x93, 0x97, 0x9d, 0x4a, 0xde, 0xce, 0xe0, 0xf6, 0x54, 0xb8, 0x5e, 0x3d, 0xab, 0x62, 0xf4, 283 | 0x89, 0xb4, 0xf8, 0xbe, 0x13, 0xf9, 0x67, 0x9d, 0xc1, 0xdd, 0xe9, 0x4f, 0xd4, 0xba, 0x63, 0x87, 284 | 0xa0, 0xe5, 0x10, 0xad, 0x80, 0xa2, 0x1d, 0xd2, 0x9c, 0x7a, 0x2f, 0xde, 0x68, 0xe6, 0xe9, 0x67, 285 | 0x85, 0x60, 0xee, 0xdb, 0xd4, 0xf5, 0x27, 0xba, 0x42, 0xbb, 0xa4, 0xc5, 0x5c, 0x7f, 0x3c, 0xf2, 286 | 0x7c, 0x57, 0x57, 0x87, 0xcf, 0xe4, 0x3e, 0xe5, 0x0b, 0x67, 0xb7, 0x8c, 0x31, 0x03, 0x8e, 0x39, 287 | 0x0a, 0x58, 0xe3, 0xd1, 0xd9, 0x38, 0x11, 0xc0, 0x93, 0x60, 0xe5, 0xec, 0x20, 0x44, 0x79, 0x7f, 288 | 0x78, 0x79, 0x40, 0x19, 0x17, 0x2f, 0x0d, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xdf, 0x1f, 0xd6, 289 | 0x6e, 0x85, 0x02, 0x00, 0x00, 290 | } 291 | -------------------------------------------------------------------------------- /protobuf/WebSocketResources.proto: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014-2016 Open Whisper Systems 3 | * 4 | * Licensed according to the LICENSE file in this repository. 5 | */ 6 | syntax = "proto2"; 7 | 8 | package signalservice; 9 | 10 | option java_package = "org.whispersystems.signalservice.internal.websocket"; 11 | option java_outer_classname = "WebSocketProtos"; 12 | 13 | message WebSocketRequestMessage { 14 | optional string verb = 1; 15 | optional string path = 2; 16 | optional bytes body = 3; 17 | repeated string headers = 5; 18 | optional uint64 id = 4; 19 | } 20 | 21 | message WebSocketResponseMessage { 22 | optional uint64 id = 1; 23 | optional uint32 status = 2; 24 | optional string message = 3; 25 | repeated string headers = 5; 26 | optional bytes body = 4; 27 | } 28 | 29 | message WebSocketMessage { 30 | enum Type { 31 | UNKNOWN = 0; 32 | REQUEST = 1; 33 | RESPONSE = 2; 34 | } 35 | 36 | optional Type type = 1; 37 | optional WebSocketRequestMessage request = 2; 38 | optional WebSocketResponseMessage response = 3; 39 | } 40 | -------------------------------------------------------------------------------- /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 | MIIF2zCCA8OgAwIBAgIUAMHz4g60cIDBpPr1gyZ/JDaaPpcwDQYJKoZIhvcNAQEL 16 | BQAwdTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT 17 | DU1vdW50YWluIFZpZXcxHjAcBgNVBAoTFVNpZ25hbCBNZXNzZW5nZXIsIExMQzEZ 18 | MBcGA1UEAxMQU2lnbmFsIE1lc3NlbmdlcjAeFw0yMjAxMjYwMDQ1NTFaFw0zMjAx 19 | MjQwMDQ1NTBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw 20 | FAYDVQQHEw1Nb3VudGFpbiBWaWV3MR4wHAYDVQQKExVTaWduYWwgTWVzc2VuZ2Vy 21 | LCBMTEMxGTAXBgNVBAMTEFNpZ25hbCBNZXNzZW5nZXIwggIiMA0GCSqGSIb3DQEB 22 | AQUAA4ICDwAwggIKAoICAQDEecifxMHHlDhxbERVdErOhGsLO08PUdNkATjZ1kT5 23 | 1uPf5JPiRbus9F4J/GgBQ4ANSAjIDZuFY0WOvG/i0qvxthpW70ocp8IjkiWTNiA8 24 | 1zQNQdCiWbGDU4B1sLi2o4JgJMweSkQFiyDynqWgHpw+KmvytCzRWnvrrptIfE4G 25 | PxNOsAtXFbVH++8JO42IaKRVlbfpe/lUHbjiYmIpQroZPGPY4Oql8KM3o39ObPnT 26 | o1WoM4moyOOZpU3lV1awftvWBx1sbTBL02sQWfHRxgNVF+Pj0fdDMMFdFJobArrL 27 | VfK2Ua+dYN4pV5XIxzVarSRW73CXqQ+2qloPW/ynpa3gRtYeGWV4jl7eD0PmeHpK 28 | OY78idP4H1jfAv0TAVeKpuB5ZFZ2szcySxrQa8d7FIf0kNJe9gIRjbQ+XrvnN+ZZ 29 | vj6d+8uBJq8LfQaFhlVfI0/aIdggScapR7w8oLpvdflUWqcTLeXVNLVrg15cEDwd 30 | lV8PVscT/KT0bfNzKI80qBq8LyRmauAqP0CDjayYGb2UAabnhefgmRY6aBE5mXxd 31 | byAEzzCS3vDxjeTD8v8nbDq+SD6lJi0i7jgwEfNDhe9XK50baK15Udc8Cr/ZlhGM 32 | jNmWqBd0jIpaZm1rzWA0k4VwXtDwpBXSz8oBFshiXs3FD6jHY2IhOR3ppbyd4qRU 33 | pwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV 34 | HQ4EFgQUtfNLxuXWS9DlgGuMUMNnW7yx83EwHwYDVR0jBBgwFoAUtfNLxuXWS9Dl 35 | gGuMUMNnW7yx83EwDQYJKoZIhvcNAQELBQADggIBABUeiryS0qjykBN75aoHO9bV 36 | PrrX+DSJIB9V2YzkFVyh/io65QJMG8naWVGOSpVRwUwhZVKh3JVp/miPgzTGAo7z 37 | hrDIoXc+ih7orAMb19qol/2Ha8OZLa75LojJNRbZoCR5C+gM8C+spMLjFf9k3JVx 38 | dajhtRUcR0zYhwsBS7qZ5Me0d6gRXD0ZiSbadMMxSw6KfKk3ePmPb9gX+MRTS63c 39 | 8mLzVYB/3fe/bkpq4RUwzUHvoZf+SUD7NzSQRQQMfvAHlxk11TVNxScYPtxXDyiy 40 | 3Cssl9gWrrWqQ/omuHipoH62J7h8KAYbr6oEIq+Czuenc3eCIBGBBfvCpuFOgckA 41 | XXE4MlBasEU0MO66GrTCgMt9bAmSw3TrRP12+ZUFxYNtqWluRU8JWQ4FCCPcz9pg 42 | MRBOgn4lTxDZG+I47OKNuSRjFEP94cdgxd3H/5BK7WHUz1tAGQ4BgepSXgmjzifF 43 | T5FVTDTl3ZnWUVBXiHYtbOBgLiSIkbqGMCLtrBtFIeQ7RRTb3L+IE9R0UB0cJB3A 44 | Xbf1lVkOcmrdu2h8A32aCwtr5S1fBF1unlG7imPmqJfpOMWa8yIF/KWVm29JAPq8 45 | Lrsybb0z5gg8w7ZblEuB9zOW9M3l60DXuJO6l7g+deV6P96rv2unHS8UlvWiVWDy 46 | 9qfgAJizyy3kqM4lOwBH 47 | -----END CERTIFICATE----- 48 | ` 49 | 50 | var rootCA *x509.CertPool 51 | 52 | func setupCA() { 53 | pem := []byte(rootPEM) 54 | if config.RootCA != "" && exists(config.RootCA) { 55 | b, err := ioutil.ReadFile(config.RootCA) 56 | if err != nil { 57 | log.Error(err) 58 | return 59 | } 60 | pem = b 61 | } 62 | 63 | rootCA = x509.NewCertPool() 64 | if !rootCA.AppendCertsFromPEM(pem) { 65 | log.Error("Cannot load PEM") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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/nanu-c/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 | "github.com/golang/protobuf/proto" 9 | "github.com/nanu-c/textsecure/protobuf" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | // handleSyncMessage handles an incoming SyncMessage. 14 | func handleSyncMessage(src string, timestamp uint64, sm *signalservice.SyncMessage) error { 15 | log.Debugf("[textsecure] SyncMessage recieved at %d", timestamp) 16 | 17 | if sm.GetSent() != nil { 18 | return handleSyncSent(sm.GetSent(), timestamp) 19 | } else if sm.GetContacts() != nil { 20 | log.Debugln("[textsecure] SyncMessage contacts") 21 | return nil 22 | } else if sm.GetGroups() != nil { 23 | log.Debugln("[textsecure] SyncMessage groups") 24 | return nil 25 | } else if sm.GetRequest() != nil { 26 | return handleSyncRequest(sm.GetRequest()) 27 | } else if sm.GetRead() != nil { 28 | return handleSyncRead(sm.GetRead()) 29 | } else if sm.GetBlocked() != nil { 30 | log.Debugln("[textsecure] SyncMessage blocked") 31 | return nil 32 | } else if sm.GetVerified() != nil { 33 | log.Debugln("[textsecure] SyncMessage verified") 34 | return nil 35 | } else if sm.GetConfiguration() != nil { 36 | log.Debugln("[textsecure] SyncMessage configuration") 37 | return nil 38 | } else if sm.GetPadding() != nil { 39 | log.Debugln("[textsecure] SyncMessage padding") 40 | return nil 41 | } else if sm.GetStickerPackOperation() != nil { 42 | log.Debugln("[textsecure] SyncMessage GetStickerPackOperation") 43 | return nil 44 | } else if sm.GetViewOnceOpen() != nil { 45 | log.Debugln("[textsecure] SyncMessage GetViewOnceOpen") 46 | return nil 47 | } else { 48 | log.Errorf("[textsecure] SyncMessage contains no known sync types") 49 | } 50 | 51 | return nil 52 | } 53 | 54 | // handleSyncSent handles sync sent messages 55 | func handleSyncSent(s *signalservice.SyncMessage_Sent, ts uint64) error { 56 | dm := s.GetMessage() 57 | dest := s.GetDestinationE164() 58 | timestamp := s.GetTimestamp() 59 | 60 | if dm == nil { 61 | return fmt.Errorf("DataMessage was nil for SyncMessage_Sent") 62 | } 63 | 64 | flags, err := handleFlags(dest, dm) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | atts, err := handleAttachments(dm) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | gr, err := handleGroups(dest, dm) 75 | if err != nil { 76 | return err 77 | } 78 | cs, err := handleContacts(dest, dm) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | msg := &Message{ 84 | source: dest, 85 | message: dm.GetBody(), 86 | attachments: atts, 87 | group: gr, 88 | contact: cs, 89 | timestamp: timestamp, 90 | flags: flags, 91 | } 92 | 93 | if client.SyncSentHandler != nil { 94 | client.SyncSentHandler(msg, ts) 95 | } 96 | 97 | return nil 98 | } 99 | 100 | // handleSyncRequestMessage 101 | func handleSyncRequest(request *signalservice.SyncMessage_Request) error { 102 | if request.GetType() == signalservice.SyncMessage_Request_CONTACTS { 103 | return sendContactUpdate() 104 | } else if request.GetType() == signalservice.SyncMessage_Request_GROUPS { 105 | return sendGroupUpdate() 106 | } else { 107 | log.Debugln("[textsecure] handle sync request unhandled type", request.GetType()) 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // sendContactUpdate 114 | func sendContactUpdate() error { 115 | log.Debugf("[textsecure] Sending contact SyncMessage") 116 | 117 | lc, err := GetRegisteredContacts() 118 | if err != nil { 119 | return fmt.Errorf("could not get local contacts: %s", err) 120 | } 121 | 122 | var buf bytes.Buffer 123 | 124 | for _, c := range lc { 125 | cd := &signalservice.ContactDetails{ 126 | Number: &c.Tel, 127 | Uuid: &c.Uuid, 128 | Name: &c.Name, 129 | Color: &c.Color, 130 | Blocked: &c.Blocked, 131 | ExpireTimer: &c.ExpireTimer, 132 | 133 | // TODO: handle avatars 134 | } 135 | 136 | b, err := proto.Marshal(cd) 137 | if err != nil { 138 | log.Errorf("Failed to marshal contact details") 139 | continue 140 | } 141 | 142 | buf.Write(varint32(len(b))) 143 | buf.Write(b) 144 | } 145 | 146 | a, err := uploadAttachment(&buf, "application/octet-stream") 147 | if err != nil { 148 | return err 149 | } 150 | 151 | sm := &signalservice.SyncMessage{ 152 | Contacts: &signalservice.SyncMessage_Contacts{ 153 | Blob: &signalservice.AttachmentPointer{ 154 | Id: &a.id, 155 | ContentType: &a.ct, 156 | Key: a.keys, 157 | }, 158 | }, 159 | } 160 | 161 | _, err = sendSyncMessage(sm) 162 | return err 163 | } 164 | 165 | // sendGroupUpdate 166 | func sendGroupUpdate() error { 167 | log.Debugf("Sending group SyncMessage") 168 | 169 | var buf bytes.Buffer 170 | 171 | for _, g := range groups { 172 | gd := &signalservice.GroupDetails{ 173 | Id: g.ID, 174 | Name: &g.Name, 175 | MembersE164: g.Members, 176 | // XXX add support for avatar 177 | // XXX add support for active? 178 | } 179 | 180 | b, err := proto.Marshal(gd) 181 | if err != nil { 182 | log.Errorf("Failed to marshal group details") 183 | continue 184 | } 185 | 186 | buf.Write(varint32(len(b))) 187 | buf.Write(b) 188 | } 189 | 190 | a, err := uploadAttachment(&buf, "application/octet-stream") 191 | if err != nil { 192 | return err 193 | } 194 | 195 | sm := &signalservice.SyncMessage{ 196 | Groups: &signalservice.SyncMessage_Groups{ 197 | Blob: &signalservice.AttachmentPointer{ 198 | Id: &a.id, 199 | ContentType: &a.ct, 200 | Key: a.keys, 201 | }, 202 | }, 203 | } 204 | 205 | _, err = sendSyncMessage(sm) 206 | return err 207 | } 208 | 209 | func handleSyncRead(readMessages []*signalservice.SyncMessage_Read) error { 210 | if client.SyncReadHandler != nil { 211 | for _, s := range readMessages { 212 | client.SyncReadHandler(s.GetSenderE164(), s.GetTimestamp()) 213 | } 214 | } 215 | 216 | return nil 217 | } 218 | 219 | // Encodes a 32bit base 128 variable-length integer and returns the bytes 220 | func varint32(value int) []byte { 221 | buf := make([]byte, binary.MaxVarintLen32) 222 | n := binary.PutUvarint(buf, uint64(value)) 223 | return buf[:n] 224 | } 225 | -------------------------------------------------------------------------------- /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 | "github.com/nanu-c/textsecure/axolotl" 22 | "github.com/nanu-c/textsecure/protobuf" 23 | log "github.com/sirupsen/logrus" 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 | digest []byte 101 | size uint32 102 | } 103 | 104 | type outgoingMessage struct { 105 | tel string 106 | msg string 107 | group *groupMessage 108 | attachment *att 109 | flags uint32 110 | } 111 | 112 | // LinkedDevices returns the list of linked devices 113 | func LinkedDevices() ([]DeviceInfo, error) { 114 | return getLinkedDevices() 115 | } 116 | 117 | // UnlinkDevice removes a linked device 118 | func UnlinkDevice(id int) error { 119 | return unlinkDevice(id) 120 | } 121 | 122 | // NewDeviceVerificationCode returns the verification code for linking devices 123 | func NewDeviceVerificationCode() (string, error) { 124 | return getNewDeviceVerificationCode() 125 | } 126 | 127 | // AddDevice links a new device 128 | func AddDevice(ephemeralId, publicKey, verificationCode string) error { 129 | return addNewDevice(ephemeralId, publicKey, verificationCode) 130 | } 131 | 132 | // SendMessage sends the given text message to the given contact. 133 | func SendMessage(tel, msg string) (uint64, error) { 134 | omsg := &outgoingMessage{ 135 | tel: tel, 136 | msg: msg, 137 | } 138 | return sendMessage(omsg) 139 | } 140 | 141 | func MIMETypeFromReader(r io.Reader) (mime string, reader io.Reader) { 142 | var buf bytes.Buffer 143 | io.CopyN(&buf, r, 1024) 144 | mime = mimemagic.Match("", buf.Bytes()) 145 | return mime, io.MultiReader(&buf, r) 146 | } 147 | 148 | // SendAttachment sends the contents of a reader, along 149 | // with an optional message to a given contact. 150 | func SendAttachment(tel, msg string, r io.Reader) (uint64, error) { 151 | ct, r := MIMETypeFromReader(r) 152 | a, err := uploadAttachment(r, ct) 153 | if err != nil { 154 | return 0, err 155 | } 156 | omsg := &outgoingMessage{ 157 | tel: tel, 158 | msg: msg, 159 | attachment: a, 160 | } 161 | return sendMessage(omsg) 162 | } 163 | 164 | // EndSession terminates the session with the given peer. 165 | func EndSession(tel string, msg string) (uint64, error) { 166 | omsg := &outgoingMessage{ 167 | tel: tel, 168 | msg: msg, 169 | flags: uint32(signalservice.DataMessage_END_SESSION), 170 | } 171 | ts, err := sendMessage(omsg) 172 | if err != nil { 173 | return 0, err 174 | } 175 | textSecureStore.DeleteAllSessions(recID(tel)) 176 | return ts, nil 177 | } 178 | 179 | // Attachment represents an attachment received from a peer 180 | type Attachment struct { 181 | R io.Reader 182 | MimeType string 183 | } 184 | 185 | // Message represents a message received from the peer. 186 | // It can optionally include attachments and be sent to a group. 187 | type Message struct { 188 | source string 189 | message string 190 | attachments []*Attachment 191 | group *Group 192 | flags uint32 193 | xpireTimer uint32 194 | ProfileKey []byte 195 | timestamp uint64 196 | quote signalservice.DataMessage_Quote 197 | contact []*signalservice.DataMessage_Contact 198 | preview []*signalservice.DataMessage_Preview 199 | sticker signalservice.DataMessage_Sticker 200 | requiredProtocolVersion uint32 201 | isViewOnce bool 202 | } 203 | 204 | // Source returns the ID of the sender of the message. 205 | func (m *Message) Source() string { 206 | return m.source 207 | } 208 | 209 | // Message returns the message body. 210 | func (m *Message) Message() string { 211 | return m.message 212 | } 213 | 214 | // Attachments returns the list of attachments on the message. 215 | func (m *Message) Attachments() []*Attachment { 216 | return m.attachments 217 | } 218 | 219 | // Group returns group information. 220 | func (m *Message) Group() *Group { 221 | return m.group 222 | } 223 | 224 | // Timestamp returns the timestamp of the message 225 | func (m *Message) Timestamp() uint64 { 226 | return m.timestamp 227 | } 228 | 229 | // Flags returns the flags in the message 230 | func (m *Message) Flags() uint32 { 231 | return m.flags 232 | } 233 | 234 | func (m *Message) XpireTimer() uint32 { 235 | return m.xpireTimer 236 | } 237 | 238 | // Client contains application specific data and callbacks. 239 | type Client struct { 240 | GetPhoneNumber func() string 241 | GetVerificationCode func() string 242 | GetStoragePassword func() string 243 | GetConfig func() (*Config, error) 244 | GetLocalContacts func() ([]Contact, error) 245 | MessageHandler func(*Message) 246 | TypingMessageHandler func(*Message) 247 | ReceiptMessageHandler func(*Message) 248 | ReceiptHandler func(string, uint32, uint64) 249 | SyncReadHandler func(string, uint64) 250 | SyncSentHandler func(*Message, uint64) 251 | RegistrationDone func() 252 | } 253 | 254 | var ( 255 | config *Config 256 | client *Client 257 | ) 258 | 259 | // setupLogging sets the logging verbosity level based on configuration 260 | // and environment variables 261 | func setupLogging() { 262 | loglevel := config.LogLevel 263 | if loglevel == "" { 264 | loglevel = os.Getenv("TEXTSECURE_LOGLEVEL") 265 | } 266 | 267 | log.SetLevel(log.DebugLevel) 268 | // switch strings.ToUpper(loglevel) { 269 | // case "DEBUG": 270 | // log.SetLevel(log.DebugLevel) 271 | // case "INFO": 272 | // log.SetLevel(log.InfoLevel) 273 | // case "WARN": 274 | // log.SetLevel(log.WarnLevel) 275 | // case "ERROR": 276 | // log.SetLevel(log.ErrorLevel) 277 | // default: 278 | // log.SetLevel(log.ErrorLevel) 279 | // } 280 | 281 | log.SetFormatter(&log.TextFormatter{ 282 | FullTimestamp: true, 283 | TimestampFormat: "2006/01/02 15:04:05", 284 | }) 285 | } 286 | 287 | // Setup initializes the package. 288 | func Setup(c *Client) error { 289 | var err error 290 | 291 | client = c 292 | 293 | config, err = loadConfig() 294 | if err != nil { 295 | return err 296 | } 297 | 298 | setupLogging() 299 | err = setupStore() 300 | if err != nil { 301 | return err 302 | } 303 | 304 | if needsRegistration() { 305 | registrationInfo.registrationID = generateRegistrationID() 306 | textSecureStore.SetLocalRegistrationID(registrationInfo.registrationID) 307 | 308 | registrationInfo.password = generatePassword() 309 | textSecureStore.storeHTTPPassword(registrationInfo.password) 310 | 311 | registrationInfo.signalingKey = generateSignalingKey() 312 | textSecureStore.storeHTTPSignalingKey(registrationInfo.signalingKey) 313 | 314 | identityKey = axolotl.GenerateIdentityKeyPair() 315 | err := textSecureStore.SetIdentityKeyPair(identityKey) 316 | if err != nil { 317 | return err 318 | } 319 | 320 | err = registerDevice() 321 | if err != nil { 322 | return err 323 | } 324 | } 325 | registrationInfo.registrationID, err = textSecureStore.GetLocalRegistrationID() 326 | if err != nil { 327 | return err 328 | } 329 | registrationInfo.password, err = textSecureStore.loadHTTPPassword() 330 | if err != nil { 331 | return err 332 | } 333 | registrationInfo.signalingKey, err = textSecureStore.loadHTTPSignalingKey() 334 | if err != nil { 335 | return err 336 | } 337 | client.RegistrationDone() 338 | setupTransporter() 339 | setupCDNTransporter() 340 | identityKey, err = textSecureStore.GetIdentityKeyPair() 341 | return err 342 | } 343 | 344 | func registerDevice() error { 345 | if config.Tel == "" { 346 | config.Tel = client.GetPhoneNumber() 347 | } 348 | setupTransporter() 349 | code, err := requestCode(config.Tel, config.VerificationType) 350 | if err != nil { 351 | return err 352 | } 353 | if config.VerificationType != "dev" { 354 | code = client.GetVerificationCode() 355 | } 356 | err = verifyCode(code) 357 | if err != nil { 358 | return err 359 | } 360 | err = generatePreKeys() 361 | if err != nil { 362 | return err 363 | } 364 | err = generatePreKeyState() 365 | if err != nil { 366 | return err 367 | } 368 | err = registerPreKeys() 369 | if err != nil { 370 | return err 371 | } 372 | client.RegistrationDone() 373 | if client.RegistrationDone != nil { 374 | fmt.Println("RegistrationDone__") 375 | client.RegistrationDone() 376 | } 377 | return nil 378 | } 379 | 380 | func handleReceipt(env *signalservice.Envelope) { 381 | if client.ReceiptHandler != nil { 382 | client.ReceiptHandler(env.GetSourceE164(), env.GetSourceDevice(), env.GetTimestamp()) 383 | } 384 | } 385 | 386 | func recID(source string) string { 387 | return source[1:] 388 | } 389 | 390 | func handleMessage(src string, timestamp uint64, b []byte) error { 391 | b = stripPadding(b) 392 | 393 | content := &signalservice.Content{} 394 | err := proto.Unmarshal(b, content) 395 | if err != nil { 396 | return err 397 | } 398 | 399 | if dm := content.GetDataMessage(); dm != nil { 400 | return handleDataMessage(src, timestamp, dm) 401 | } else if sm := content.GetSyncMessage(); sm != nil && config.Tel == src { 402 | return handleSyncMessage(src, timestamp, sm) 403 | } else if cm := content.GetCallMessage(); cm != nil { 404 | return handleCallMessage(src, timestamp, cm) 405 | } else if rm := content.GetReceiptMessage(); rm != nil { 406 | return handleReceiptMessage(src, timestamp, rm) 407 | } else if tm := content.GetTypingMessage(); tm != nil { 408 | return handleTypingMessage(src, timestamp, tm) 409 | } 410 | 411 | //FIXME get the right content 412 | // log.Errorf(content) 413 | log.Errorln("Unknown message content received", content) 414 | return nil 415 | } 416 | 417 | // EndSessionFlag signals that this message resets the session 418 | var EndSessionFlag uint32 = 1 419 | 420 | func handleFlags(src string, dm *signalservice.DataMessage) (uint32, error) { 421 | flags := uint32(0) 422 | if dm.GetFlags() == uint32(signalservice.DataMessage_END_SESSION) { 423 | flags = EndSessionFlag 424 | textSecureStore.DeleteAllSessions(recID(src)) 425 | } 426 | return flags, nil 427 | } 428 | 429 | // handleDataMessage handles an incoming DataMessage and calls client callbacks 430 | func handleDataMessage(src string, timestamp uint64, dm *signalservice.DataMessage) error { 431 | flags, err := handleFlags(src, dm) 432 | if err != nil { 433 | return err 434 | } 435 | 436 | atts, err := handleAttachments(dm) 437 | if err != nil { 438 | return err 439 | } 440 | log.Debugln("[textsecure] handleDataMessage") 441 | gr, err := handleGroups(src, dm) 442 | if err != nil { 443 | return err 444 | } 445 | if err != nil { 446 | return err 447 | } 448 | msg := &Message{ 449 | source: src, 450 | message: dm.GetBody(), 451 | attachments: atts, 452 | group: gr, 453 | timestamp: timestamp, 454 | flags: flags, 455 | } 456 | 457 | if client.MessageHandler != nil { 458 | client.MessageHandler(msg) 459 | } 460 | return nil 461 | } 462 | func handleCallMessage(src string, timestamp uint64, cm *signalservice.CallMessage) error { 463 | 464 | msg := &Message{ 465 | source: src, 466 | message: "callMessage", 467 | timestamp: timestamp, 468 | } 469 | 470 | if client.MessageHandler != nil { 471 | client.MessageHandler(msg) 472 | } 473 | return nil 474 | } 475 | func handleTypingMessage(src string, timestamp uint64, cm *signalservice.TypingMessage) error { 476 | 477 | msg := &Message{ 478 | source: src, 479 | message: "typingMessage", 480 | timestamp: timestamp, 481 | } 482 | 483 | if client.MessageHandler != nil { 484 | client.TypingMessageHandler(msg) 485 | } 486 | return nil 487 | } 488 | func handleReceiptMessage(src string, timestamp uint64, cm *signalservice.ReceiptMessage) error { 489 | 490 | msg := &Message{ 491 | source: src, 492 | message: "receiptMessage", 493 | timestamp: timestamp, 494 | } 495 | 496 | if client.MessageHandler != nil { 497 | client.ReceiptMessageHandler(msg) 498 | } 499 | return nil 500 | } 501 | 502 | // MessageTypeNotImplementedError is raised in the unlikely event that an unhandled protocol message type is received. 503 | type MessageTypeNotImplementedError struct { 504 | typ uint32 505 | } 506 | 507 | func (err MessageTypeNotImplementedError) Error() string { 508 | return fmt.Sprintf("not implemented message type %d", err.typ) 509 | } 510 | 511 | // ErrInvalidMACForMessage signals an incoming message with invalid MAC. 512 | var ErrInvalidMACForMessage = errors.New("invalid MAC for incoming message") 513 | 514 | // Authenticate and decrypt a received message 515 | func handleReceivedMessage(msg []byte) error { 516 | macpos := len(msg) - 10 517 | tmac := msg[macpos:] 518 | aesKey := registrationInfo.signalingKey[:32] 519 | macKey := registrationInfo.signalingKey[32:] 520 | if !axolotl.ValidTruncMAC(msg[:macpos], tmac, macKey) { 521 | return ErrInvalidMACForMessage 522 | } 523 | ciphertext := msg[1:macpos] 524 | plaintext, err := axolotl.Decrypt(aesKey, ciphertext) 525 | if err != nil { 526 | return err 527 | } 528 | env := &signalservice.Envelope{} 529 | err = proto.Unmarshal(plaintext, env) 530 | if err != nil { 531 | return err 532 | } 533 | recid := recID(env.GetSourceE164()) 534 | sc := axolotl.NewSessionCipher(textSecureStore, textSecureStore, textSecureStore, textSecureStore, recid, env.GetSourceDevice()) 535 | switch *env.Type { 536 | case signalservice.Envelope_RECEIPT: 537 | handleReceipt(env) 538 | return nil 539 | case signalservice.Envelope_CIPHERTEXT: 540 | msg := env.GetContent() 541 | if msg == nil { 542 | return errors.New("Legacy messages unsupported") 543 | } 544 | wm, err := axolotl.LoadWhisperMessage(msg) 545 | if err != nil { 546 | return err 547 | } 548 | b, err := sc.SessionDecryptWhisperMessage(wm) 549 | if _, ok := err.(axolotl.DuplicateMessageError); ok { 550 | log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) 551 | return nil 552 | } 553 | if _, ok := err.(axolotl.InvalidMessageError); ok { 554 | log.Infof("Incoming WhisperMessage %s. Ignoring.\n", err) 555 | return nil 556 | } 557 | if err != nil { 558 | return err 559 | } 560 | err = handleMessage(env.GetSourceE164(), env.GetTimestamp(), b) 561 | if err != nil { 562 | return err 563 | } 564 | 565 | case signalservice.Envelope_PREKEY_BUNDLE: 566 | msg := env.GetContent() 567 | pkwm, err := axolotl.LoadPreKeyWhisperMessage(msg) 568 | if err != nil { 569 | return err 570 | } 571 | b, err := sc.SessionDecryptPreKeyWhisperMessage(pkwm) 572 | if _, ok := err.(axolotl.DuplicateMessageError); ok { 573 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 574 | return nil 575 | } 576 | if _, ok := err.(axolotl.PreKeyNotFoundError); ok { 577 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 578 | return nil 579 | } 580 | if _, ok := err.(axolotl.InvalidMessageError); ok { 581 | log.Infof("Incoming PreKeyWhisperMessage %s. Ignoring.\n", err) 582 | return nil 583 | } 584 | if err != nil { 585 | return err 586 | } 587 | err = handleMessage(env.GetSourceE164(), env.GetTimestamp(), b) 588 | if err != nil { 589 | return err 590 | } 591 | default: 592 | return MessageTypeNotImplementedError{uint32(*env.Type)} 593 | } 594 | 595 | return nil 596 | } 597 | -------------------------------------------------------------------------------- /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 | "time" 14 | 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | var transport transporter 19 | 20 | func setupTransporter() { 21 | setupCA() 22 | transport = newHTTPTransporter(config.Server, config.Tel, registrationInfo.password) 23 | } 24 | 25 | type response struct { 26 | Status int 27 | Body io.ReadCloser 28 | } 29 | 30 | func (r *response) isError() bool { 31 | return r.Status < 200 || r.Status >= 300 32 | } 33 | 34 | func (r *response) Error() string { 35 | return fmt.Sprintf("status code %d\n", r.Status) 36 | } 37 | 38 | type transporter interface { 39 | get(url string) (*response, error) 40 | del(url string) (*response, error) 41 | putJSON(url string, body []byte) (*response, error) 42 | putBinary(url string, body []byte) (*response, error) 43 | } 44 | 45 | type httpTransporter struct { 46 | baseURL string 47 | user string 48 | pass string 49 | client *http.Client 50 | } 51 | 52 | func getProxy(req *http.Request) (*url.URL, error) { 53 | if config.ProxyServer != "" { 54 | u, err := url.Parse(config.ProxyServer) 55 | if err == nil { 56 | return u, nil 57 | } 58 | } 59 | return http.ProxyFromEnvironment(req) 60 | } 61 | 62 | func newHTTPClient() *http.Client { 63 | client := &http.Client{ 64 | Transport: &http.Transport{ 65 | TLSHandshakeTimeout: 30 * time.Second, 66 | }, 67 | Timeout: 45 * time.Second, 68 | } 69 | 70 | return client 71 | } 72 | 73 | func newHTTPTransporter(baseURL, user, pass string) *httpTransporter { 74 | client := &http.Client{ 75 | Timeout: 15 * time.Second, 76 | Transport: &http.Transport{ 77 | TLSClientConfig: &tls.Config{RootCAs: rootCA}, 78 | Proxy: getProxy, 79 | }, 80 | } 81 | 82 | return &httpTransporter{baseURL, user, pass, client} 83 | } 84 | 85 | func (ht *httpTransporter) get(url string) (*response, error) { 86 | req, err := http.NewRequest("GET", ht.baseURL+url, nil) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if config.UserAgent != "" { 91 | req.Header.Set("X-Signal-Agent", config.UserAgent) 92 | } 93 | req.SetBasicAuth(ht.user, ht.pass) 94 | resp, err := ht.client.Do(req) 95 | if err != nil { 96 | return nil, err 97 | } 98 | r := &response{} 99 | if resp != nil { 100 | r.Status = resp.StatusCode 101 | r.Body = resp.Body 102 | } 103 | 104 | log.Debugf("GET %s %d\n", url, r.Status) 105 | 106 | return r, err 107 | } 108 | 109 | func (ht *httpTransporter) del(url string) (*response, error) { 110 | req, err := http.NewRequest("DELETE", ht.baseURL+url, nil) 111 | if err != nil { 112 | return nil, err 113 | } 114 | if config.UserAgent != "" { 115 | req.Header.Set("X-Signal-Agent", config.UserAgent) 116 | } 117 | req.SetBasicAuth(ht.user, ht.pass) 118 | resp, err := ht.client.Do(req) 119 | if err != nil { 120 | return nil, err 121 | } 122 | r := &response{} 123 | if resp != nil { 124 | r.Status = resp.StatusCode 125 | r.Body = resp.Body 126 | } 127 | 128 | log.Debugf("DELETE %s %d\n", url, r.Status) 129 | 130 | return r, err 131 | } 132 | 133 | func (ht *httpTransporter) put(url string, body []byte, ct string) (*response, error) { 134 | br := bytes.NewReader(body) 135 | req, err := http.NewRequest("PUT", ht.baseURL+url, br) 136 | if err != nil { 137 | return nil, err 138 | } 139 | if config.UserAgent != "" { 140 | req.Header.Set("X-Signal-Agent", config.UserAgent) 141 | } 142 | req.Header.Add("Content-Type", ct) 143 | req.SetBasicAuth(ht.user, ht.pass) 144 | resp, err := ht.client.Do(req) 145 | if err != nil { 146 | return nil, err 147 | } 148 | r := &response{} 149 | if resp != nil { 150 | r.Status = resp.StatusCode 151 | r.Body = resp.Body 152 | } 153 | 154 | log.Debugf("[textsecure] PUT %s %d\n", url, r.Status) 155 | 156 | return r, err 157 | } 158 | 159 | func (ht *httpTransporter) putJSON(url string, body []byte) (*response, error) { 160 | return ht.put(url, body, "application/json") 161 | } 162 | 163 | func (ht *httpTransporter) putBinary(url string, body []byte) (*response, error) { 164 | return ht.put(url, body, "application/octet-stream") 165 | } 166 | -------------------------------------------------------------------------------- /websocket.go: -------------------------------------------------------------------------------- 1 | package textsecure 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "strings" 10 | "time" 11 | 12 | "github.com/golang/protobuf/proto" 13 | "github.com/gorilla/websocket" 14 | "github.com/nanu-c/textsecure/protobuf" 15 | 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | const ( 20 | // Time allowed to write a message to the peer. 21 | writeWait = 25 * time.Second 22 | 23 | // Time allowed to read the next pong message from the peer. 24 | pongWait = 60 * time.Second 25 | 26 | // Send pings to peer with this period. Must be less than pongWait. 27 | pingPeriod = (pongWait * 9) / 10 28 | 29 | // Signal websocket endpoint 30 | websocketPath = "/v1/websocket/" 31 | ) 32 | 33 | // Conn is a wrapper for the websocket connection 34 | type Conn struct { 35 | // The websocket connection 36 | ws *websocket.Conn 37 | 38 | // Buffered channel of outbound messages 39 | send chan []byte 40 | } 41 | 42 | var wsconn *Conn 43 | 44 | // Connect to Signal websocket API at originURL with user and pass credentials 45 | func (c *Conn) connect(originURL, user, pass string) error { 46 | v := url.Values{} 47 | v.Set("login", user) 48 | v.Set("password", pass) 49 | params := v.Encode() 50 | wsURL := strings.Replace(originURL, "http", "ws", 1) + "?" + params 51 | u, _ := url.Parse(wsURL) 52 | 53 | log.Debugf("[textsecure] Websocket Connecting to signal-server") 54 | // log.Debugf("Websocket Connecting to %s with user %s and pass %s", originURL, user, pass) 55 | 56 | var err error 57 | d := &websocket.Dialer{ 58 | ReadBufferSize: 1024, 59 | WriteBufferSize: 1024, 60 | } 61 | d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) } 62 | d.TLSClientConfig = &tls.Config{RootCAs: rootCA} 63 | 64 | c.ws, _, err = d.Dial(u.String(), nil) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | log.Debugf("[textsecure] Websocket Connected successfully") 70 | 71 | return nil 72 | } 73 | 74 | // Send ack response message 75 | func (c *Conn) sendAck(id uint64) error { 76 | typ := signalservice.WebSocketMessage_RESPONSE 77 | message := "OK" 78 | status := uint32(200) 79 | 80 | wsm := &signalservice.WebSocketMessage{ 81 | Type: &typ, 82 | Response: &signalservice.WebSocketResponseMessage{ 83 | Id: &id, 84 | Status: &status, 85 | Message: &message, 86 | }, 87 | } 88 | 89 | b, err := proto.Marshal(wsm) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | c.send <- b 95 | return nil 96 | } 97 | 98 | // write writes a message with the given message type and payload. 99 | func (c *Conn) write(mt int, payload []byte) error { 100 | c.ws.SetWriteDeadline(time.Now().Add(writeWait)) 101 | return c.ws.WriteMessage(mt, payload) 102 | } 103 | 104 | // writeWorker writes messages to websocket connection 105 | func (c *Conn) writeWorker() { 106 | ticker := time.NewTicker(pingPeriod) 107 | defer func() { 108 | log.Debugf("[textsecure] Closing writeWorker") 109 | ticker.Stop() 110 | c.ws.Close() 111 | }() 112 | for { 113 | select { 114 | case message, ok := <-c.send: 115 | if !ok { 116 | log.Errorf("[textsecure] Failed to read message from channel") 117 | c.write(websocket.CloseMessage, []byte{}) 118 | return 119 | } 120 | 121 | log.Debugf("[textsecure] Websocket sending message") 122 | if err := c.write(websocket.BinaryMessage, message); err != nil { 123 | log.WithFields(log.Fields{ 124 | "error": err, 125 | }).Error("[textsecure] Failed to send websocket message") 126 | return 127 | } 128 | case <-ticker.C: 129 | log.Debugf("[textsecure] Sending websocket ping message") 130 | if err := c.write(websocket.PingMessage, nil); err != nil { 131 | log.WithFields(log.Fields{ 132 | "error": err, 133 | }).Error("[textsecure] Failed to send websocket ping message") 134 | return 135 | } 136 | } 137 | } 138 | } 139 | 140 | // StartListening connects to the server and handles incoming websocket messages. 141 | func StartListening() error { 142 | var err error 143 | 144 | wsconn = &Conn{send: make(chan []byte, 256)} 145 | 146 | err = wsconn.connect(config.Server+websocketPath, config.Tel, registrationInfo.password) 147 | if err != nil { 148 | log.Errorf(err.Error()) 149 | return err 150 | } 151 | 152 | defer wsconn.ws.Close() 153 | 154 | // Can only have a single goroutine call write methods 155 | go wsconn.writeWorker() 156 | 157 | wsconn.ws.SetReadDeadline(time.Now().Add(pongWait)) 158 | wsconn.ws.SetPongHandler(func(string) error { 159 | log.Debugf("[textsecure] Received websocket pong message") 160 | wsconn.ws.SetReadDeadline(time.Now().Add(pongWait)) 161 | return nil 162 | }) 163 | 164 | for { 165 | _, bmsg, err := wsconn.ws.ReadMessage() 166 | if err != nil { 167 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { 168 | log.Debugf("[textsecure] Websocket UnexpectedCloseError: %s", err) 169 | } 170 | return err 171 | } 172 | 173 | wsm := &signalservice.WebSocketMessage{} 174 | err = proto.Unmarshal(bmsg, wsm) 175 | if err != nil { 176 | log.WithFields(log.Fields{ 177 | "error": err, 178 | }).Error("[textsecure] Failed to unmarshal websocket message") 179 | return err 180 | } 181 | 182 | m := wsm.GetRequest().GetBody() 183 | 184 | if len(m) > 0 { 185 | err = handleReceivedMessage(m) 186 | if err != nil { 187 | log.WithFields(log.Fields{ 188 | "error": err, 189 | }).Error("[textsecure] Failed to handle received message") 190 | } 191 | } else { 192 | log.Debugln(wsm.GetRequest()) 193 | if wsm.GetRequest().GetPath() == "/api/v1/queue/empty" { 194 | log.Println("[textsecure] No new messages") 195 | } else { 196 | log.WithFields(log.Fields{ 197 | "source": wsm.GetRequest().GetId(), 198 | }).Warn("[textsecure] Zero byte message received. Ignoring") 199 | } 200 | 201 | } 202 | 203 | err = wsconn.sendAck(wsm.GetRequest().GetId()) 204 | if err != nil { 205 | log.WithFields(log.Fields{ 206 | "error": err, 207 | }).Error("[textsecure] Failed to send ack") 208 | return err 209 | } 210 | 211 | } 212 | 213 | return fmt.Errorf("[textsecure] Connection closed") 214 | } 215 | 216 | // ErrNotListening is returned when trying to stop listening when there's no 217 | // valid listening connection set up 218 | var ErrNotListening = errors.New("[textsecure] there is no listening connection to stop") 219 | 220 | // StopListening disables the receiving of messages. 221 | func StopListening() error { 222 | if wsconn == nil { 223 | return ErrNotListening 224 | } 225 | 226 | if wsconn.ws != nil { 227 | wsconn.ws.Close() 228 | } 229 | 230 | return nil 231 | } 232 | --------------------------------------------------------------------------------