Skip to content

Commit

Permalink
add support for profile keys
Browse files Browse the repository at this point in the history
  • Loading branch information
[email protected] committed Feb 26, 2021
1 parent b2805fa commit 99f0b9e
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 10 deletions.
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Config struct {
AlwaysTrustPeerID bool `yaml:"alwaysTrustPeerID"` // Workaround until proper handling of peer reregistering with new ID.
AccountCapabilities AccountCapabilities `yaml:"accountCapabilities"` // Account Attrributes are used in order to track the support of different function for signal
DiscoverableByPhoneNumber bool `yaml:"discoverableByPhoneNumber"` // If the user should be found by his phone number
ProfileKey []byte `yaml:"profileKey"` // The profile key is used in many places to encrypt the avatar, name etc and also in groupsv2 context
Name string `yaml:"name"`
}

// TODO: some race conditions to be solved
Expand Down
16 changes: 16 additions & 0 deletions crypto/aesgcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

const TAG_LENGTH_BYTES = 16

// AesgcmDecrypt ...
func AesgcmDecrypt(key, nonce, data, mac []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
Expand All @@ -21,3 +22,18 @@ func AesgcmDecrypt(key, nonce, data, mac []byte) ([]byte, error) {

return aesgcm.Open(nil, nonce, ciphertext, nil)
}

//indirect tested by profile_cipher_test.go

//AesgcmEncrypt ...
func AesgcmEncrypt(key, nonce, input []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return aesgcm.Seal(nil, nonce, input, nil), nil
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ require (
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/opennota/check v0.0.0-20180911053232-0c771f5545ff // indirect
github.com/pieterbork/ed25519 v0.0.0-20200301051623-f19b832d0d2e // indirect
github.com/satori/go.uuid v1.2.0
github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 // indirect
github.com/signal-golang/ed25519 v0.0.0-20200301051623-f19b832d0d2e
github.com/signal-golang/mimemagic v0.0.0-20200821045537-3f613cf2cd3f
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/testify v1.5.1
github.com/stretchr/testify v1.6.1
github.com/stripe/safesql v0.2.0 // indirect
github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 // indirect
github.com/walle/lll v1.0.1 // indirect
Expand Down
116 changes: 116 additions & 0 deletions profiles/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package profiles

import (
"encoding/json"
"errors"
"fmt"
"math/rand"

zkgroup "github.com/nanu-c/zkgroup"
uuidUtil "github.com/satori/go.uuid"
"github.com/signal-golang/textsecure/crypto"
"github.com/signal-golang/textsecure/transport"
)

const (
PROFILE_PATH = "/v1/profile/%s"
NAME_PADDED_LENGTH = 53
)

// Profile ...
type Profile struct {
Version string `json:"version"`
Name []byte `json:"name"`
Avatar bool `json:"avatar"`
Commitment []byte `json:"commitment"`
}

// GenerateProfileKey generates a new ProfileKey
func GenerateProfileKey() []byte {
profileKey := make([]byte, 32)
rand.Read(profileKey)
return profileKey
}
func uuidToByte(id string) []byte {
s, _ := uuidUtil.FromString(id)
return s.Bytes()
}

// UpdateProfile ...
func UpdateProfile(profileKey []byte, uuid, name string) error {
uuidByte := uuidToByte(uuid)

profile := Profile{}
version, err := zkgroup.ProfileKeyGetProfileKeyVersion(profileKey, uuidByte)
if err != nil {
return err
}
commitment, err := zkgroup.ProfileKeyGetCommitment(profileKey, uuidByte)
if err != nil {
return err
}
nameCiphertext, err := encryptName(profileKey, []byte(name), NAME_PADDED_LENGTH)
if err != nil {
return err
}
profile.Version = string(version[:])
profile.Commitment = commitment
profile.Name = nameCiphertext
profile.Avatar = false
writeProfile(profile)
if err != nil {
return err
}
return nil
}

// WriteProfile ...
func writeProfile(profile Profile) error {
// String response = makeServiceRequest(String.format(PROFILE_PATH, ""), "PUT", requestBody);
body, err := json.Marshal(profile)
if err != nil {
return err
}
transport.Transport.PutJSON(fmt.Sprintf(PROFILE_PATH, ""), body)
return nil
}

func encryptName(key, input []byte, paddedLength int) ([]byte, error) {
inputLength := len(input)
if inputLength > paddedLength {
return nil, errors.New("Input too long")
}
padded := append(input, make([]byte, paddedLength-inputLength)...)
nonce := make([]byte, 12)
rand.Read(nonce)
ciphertext, err := crypto.AesgcmEncrypt(key, nonce, padded)
if err != nil {
return nil, err
}
return append(nonce, ciphertext...), nil
}

func decryptName(key, nonceAndCiphertext []byte) ([]byte, error) {
if len(nonceAndCiphertext) < 12+16+1 {
return nil, errors.New("nonceAndCipher too short")
}
nonce := nonceAndCiphertext[:12]
ciphertext := nonceAndCiphertext[12:]
padded, err := crypto.AesgcmDecrypt(key, nonce, ciphertext, []byte{})
if err != nil {
return nil, err
}
paddedLength := len(padded)
plaintextLength := 0
for i := paddedLength - 1; i >= 0; i-- {
if padded[i] != byte(0) {
plaintextLength = i + 1
break
}
}
return padded[:plaintextLength], nil
}

func getCommitment(profileKey, uuid []byte) ([]byte, error) {
return zkgroup.ProfileKeyGetCommitment(profileKey, uuid)
}
41 changes: 41 additions & 0 deletions profiles/profile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package textsecure
import (
"testing"
"github.com/stretchr/testify/assert"
"math/rand"
)
func TestEncryptDecrypt(t *testing.T) {
key := make([]byte, 32)
rand.Read(key)

name, err := encryptName(key,[]byte("Clement\ruval"), NAME_PADDED_LENGTH);
assert.Nil(t, err)
plaintext, err := decryptName(key, name);
assert.Nil(t, err)

assert.Equal(t, []byte("Clement\ruval"), plaintext);
}

func TestEmpty(t *testing.T) {
key := make([]byte, 32)
rand.Read(key)

name, err := encryptName(key, []byte(""), 26);
assert.Nil(t, err)
plaintext, err := decryptName(key, name);
assert.Nil(t, err)

assert.Equal(t, 0, len(plaintext));
}

var EXPECTED_RESULT = []byte{0x5a, 0x72, 0x3a, 0xce, 0xe5, 0x2c, 0x5e, 0xa0,
0x2b, 0x92, 0xa3, 0xa3, 0x60, 0xc0, 0x95, 0x95}
var KEY = []byte{0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02}

func TestKeyDerivation(t *testing.T) {
result, err := deriveAccessKeyFrom(KEY);
assert.Nil(t, err)
assert.Equal(t, EXPECTED_RESULT, result);
}
20 changes: 13 additions & 7 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
SIGNAL_CDN_URL = "https://cdn.signal.org"
SIGNAL_CDN2_URL = "https://cdn2.signal.org"
DIRECTORY_URL = "https://api.directory.signal.org"
STORAGE_URL = "https://storage.signal.org"

createAccountPath = "/v1/accounts/%s/code/%s?client=%s"
// CREATE_ACCOUNT_SMS_PATH = "/v1/accounts/sms/code/%s?client=%s";
Expand Down Expand Up @@ -100,6 +101,9 @@ var (
GROUPSV2_GROUP_JOIN = "/v1/groups/join/%s"
GROUPSV2_TOKEN = "/v1/groups/token"

ATTESTATION_REQUEST = "/v1/attestation/%s"
DISCOVERY_REQUEST = "/v1/discovery/%s"

SERVER_DELIVERED_TIMESTAMP_HEADER = "X-Signal-Timestamp"
CDS_MRENCLAVE = "c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15"

Expand Down Expand Up @@ -135,15 +139,15 @@ var registrationInfo RegistrationInfo

// Registration
const (
responseNeedCaptcha int = 402;
responseRateLimit int = 413
responseNeedCaptcha int = 402
responseRateLimit int = 413
)

func requestCode(tel, method, captcha string) (string,*int,error) {
func requestCode(tel, method, captcha string) (string, *int, error) {
log.Infoln("[textsecure] request verification code for ", tel)
path := fmt.Sprintf(createAccountPath, method, tel, "android")
if captcha != ""{
path +="&captcha="+captcha
if captcha != "" {
path += "&captcha=" + captcha
}
resp, err := transport.Transport.Get(path)
if err != nil {
Expand Down Expand Up @@ -175,7 +179,7 @@ func requestCode(tel, method, captcha string) (string,*int,error) {
}
} else {
defer resp.Body.Close()
return "",nil, nil
return "", nil, nil
}
// unofficial dev method, useful for development, with no telephony account needed on the server
// if method == "dev" {
Expand Down Expand Up @@ -329,6 +333,7 @@ type whoAmIResponse struct {
UUID string `json:"uuid"`
}

// GetMyUUID returns the uid from the current user
func GetMyUUID() (string, error) {
log.Debugln("[textsecure] get my uuid")
resp, err := transport.Transport.Get(WHO_AM_I)
Expand Down Expand Up @@ -407,6 +412,7 @@ func addNewDevice(ephemeralId, publicKey, verificationCode string) error {
IdentityKeyPublic: identityKey.PublicKey.Serialize(),
IdentityKeyPrivate: identityKey.PrivateKey.Key()[:],
Number: &config.Tel,
Uuid: &config.UUID,
ProvisioningCode: &verificationCode,
}

Expand Down Expand Up @@ -766,7 +772,7 @@ func createMessage(msg *outgoingMessage) *signalservice.DataMessage {
if msg.groupV2 != nil {
dm.GroupV2 = msg.groupV2
}

dm.ProfileKey = config.ProfileKey
dm.Flags = &msg.flags

return dm
Expand Down
13 changes: 12 additions & 1 deletion textsecure.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/golang/protobuf/proto"

"github.com/signal-golang/textsecure/axolotl"
"github.com/signal-golang/textsecure/profiles"
signalservice "github.com/signal-golang/textsecure/protobuf"
rootCa "github.com/signal-golang/textsecure/rootCa"
transport "github.com/signal-golang/textsecure/transport"
Expand Down Expand Up @@ -309,6 +310,7 @@ func Setup(c *Client) error {
if err != nil {
return err
}

client.RegistrationDone()
rootCa.SetupCA(config.RootCA)
transport.SetupTransporter(config.Server, config.Tel, registrationInfo.password, config.UserAgent, config.ProxyServer)
Expand All @@ -317,6 +319,11 @@ func Setup(c *Client) error {
identityKey, err = textSecureStore.GetIdentityKeyPair()
// check if we have a uuid and if not get it
config = checkUUID(config)
if len(config.ProfileKey) == 0 {
config.ProfileKey = profiles.GenerateProfileKey()
}
profiles.UpdateProfile(config.ProfileKey, config.UUID, "nanu")
GetProfile(config.UUID)
return err
}

Expand Down Expand Up @@ -379,6 +386,8 @@ func registerDevice() error {
if err != nil {
return err
}
config.ProfileKey = profiles.GenerateProfileKey()
config = checkUUID(config)
client.RegistrationDone()
if client.RegistrationDone != nil {
log.Infoln("[textsecure] RegistrationDone")
Expand Down Expand Up @@ -421,8 +430,10 @@ func handleMessage(srcE164 string, srcUUID string, timestamp uint64, b []byte) e
return handleReceiptMessage(srcE164, srcUUID, timestamp, rm)
} else if tm := content.GetTypingMessage(); tm != nil {
return handleTypingMessage(srcE164, srcUUID, timestamp, tm)
} else if nm := content.GetNullMessage(); nm != nil {
log.Errorln("[textsecure] Nullmessage content received", content)
return nil
}

//FIXME get the right content
// log.Errorf(content)
log.Errorln("[textsecure] Unknown message content received", content)
Expand Down
2 changes: 1 addition & 1 deletion transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (ht *httpTransporter) Get(url string) (*response, error) {
r.Body = resp.Body
}

log.Debugf("GET %s %d\n", url, r.Status)
log.Debugf("[textsecure] GET %s %d\n", url, r.Status)

return r, err
}
Expand Down

0 comments on commit 99f0b9e

Please sign in to comment.