Refactor more things
This commit is contained in:
parent
e6cec49353
commit
3fefda3a96
19 changed files with 138 additions and 256 deletions
|
@ -15,7 +15,6 @@ import (
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm"
|
"go.mau.fi/mautrix-gmessages/libgm"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,14 +43,12 @@ func main() {
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
sess = *libgm.NewAuthData()
|
||||||
} else {
|
} else {
|
||||||
must(json.NewDecoder(file).Decode(&sess))
|
must(json.NewDecoder(file).Decode(&sess))
|
||||||
log.Info().Msg("Loaded session?")
|
log.Info().Msg("Loaded session?")
|
||||||
}
|
}
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
if sess.Cryptor == nil {
|
|
||||||
sess.Cryptor = crypto.NewCryptor(nil, nil)
|
|
||||||
}
|
|
||||||
cli = libgm.NewClient(&sess, log)
|
cli = libgm.NewClient(&sess, log)
|
||||||
cli.SetEventHandler(evtHandler)
|
cli.SetEventHandler(evtHandler)
|
||||||
must(cli.Connect())
|
must(cli.Connect())
|
||||||
|
|
129
libgm/client.go
129
libgm/client.go
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -26,16 +27,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthData struct {
|
type AuthData struct {
|
||||||
TachyonAuthToken []byte `json:"tachyon_token,omitempty"`
|
// Keys used to encrypt communication with the phone
|
||||||
TTL int64 `json:"ttl,omitempty"`
|
RequestCrypto *crypto.AESCTRHelper `json:"request_crypto,omitempty"`
|
||||||
AuthenticatedAt *time.Time `json:"authenticated_at,omitempty"`
|
// Key used to sign requests to refresh the tachyon auth token from the server
|
||||||
|
RefreshKey *crypto.JWK `json:"refresh_key,omitempty"`
|
||||||
|
// Identity of the paired phone and browser
|
||||||
DevicePair *pblite.DevicePair `json:"device_pair,omitempty"`
|
DevicePair *pblite.DevicePair `json:"device_pair,omitempty"`
|
||||||
Cryptor *crypto.Cryptor `json:"crypto,omitempty"`
|
// Key used to authenticate with the server
|
||||||
|
TachyonAuthToken []byte `json:"tachyon_token,omitempty"`
|
||||||
|
TachyonExpiry time.Time `json:"tachyon_expiry,omitempty"`
|
||||||
|
TachyonTTL int64 `json:"tachyon_ttl,omitempty"`
|
||||||
|
// Unknown encryption key, not used for anything
|
||||||
WebEncryptionKey []byte `json:"web_encryption_key,omitempty"`
|
WebEncryptionKey []byte `json:"web_encryption_key,omitempty"`
|
||||||
JWK *crypto.JWK `json:"jwk,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RefreshTachyonBuffer = 1 * time.Hour
|
||||||
|
|
||||||
type Proxy func(*http.Request) (*url.URL, error)
|
type Proxy func(*http.Request) (*url.URL, error)
|
||||||
type EventHandler func(evt any)
|
type EventHandler func(evt any)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Logger zerolog.Logger
|
Logger zerolog.Logger
|
||||||
rpc *RPC
|
rpc *RPC
|
||||||
|
@ -49,17 +59,18 @@ type Client struct {
|
||||||
http *http.Client
|
http *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAuthData() *AuthData {
|
||||||
|
return &AuthData{
|
||||||
|
RequestCrypto: crypto.NewAESCTRHelper(),
|
||||||
|
RefreshKey: crypto.GenerateECDSAKey(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
|
func NewClient(authData *AuthData, logger zerolog.Logger) *Client {
|
||||||
sessionHandler := &SessionHandler{
|
sessionHandler := &SessionHandler{
|
||||||
responseWaiters: make(map[string]chan<- *pblite.Response),
|
responseWaiters: make(map[string]chan<- *pblite.Response),
|
||||||
responseTimeout: time.Duration(5000) * time.Millisecond,
|
responseTimeout: time.Duration(5000) * time.Millisecond,
|
||||||
}
|
}
|
||||||
if authData == nil {
|
|
||||||
authData = &AuthData{}
|
|
||||||
}
|
|
||||||
if authData.Cryptor == nil {
|
|
||||||
authData.Cryptor = crypto.NewCryptor(nil, nil)
|
|
||||||
}
|
|
||||||
cli := &Client{
|
cli := &Client{
|
||||||
authData: authData,
|
authData: authData,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
@ -90,19 +101,12 @@ func (c *Client) SetProxy(proxy string) error {
|
||||||
c.Logger.Debug().Any("proxy", proxyParsed.Host).Msg("SetProxy")
|
c.Logger.Debug().Any("proxy", proxyParsed.Host).Msg("SetProxy")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Connect() error {
|
func (c *Client) Connect() error {
|
||||||
if c.authData.TachyonAuthToken != nil {
|
if c.authData.TachyonAuthToken != nil {
|
||||||
|
|
||||||
hasExpired, authenticatedAtSeconds := c.hasTachyonTokenExpired()
|
|
||||||
if hasExpired {
|
|
||||||
c.Logger.Error().Any("expired", hasExpired).Any("secondsSince", authenticatedAtSeconds).Msg("TachyonToken has expired! attempting to refresh")
|
|
||||||
refreshErr := c.refreshAuthToken()
|
refreshErr := c.refreshAuthToken()
|
||||||
if refreshErr != nil {
|
if refreshErr != nil {
|
||||||
panic(refreshErr)
|
panic(refreshErr)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
c.Logger.Info().Any("secondsSince", authenticatedAtSeconds).Any("token", c.authData.TachyonAuthToken).Msg("TachyonToken has not expired, attempting to connect...")
|
|
||||||
|
|
||||||
webEncryptionKeyResponse, webEncryptionKeyErr := c.GetWebEncryptionKey()
|
webEncryptionKeyResponse, webEncryptionKeyErr := c.GetWebEncryptionKey()
|
||||||
if webEncryptionKeyErr != nil {
|
if webEncryptionKeyErr != nil {
|
||||||
|
@ -110,13 +114,7 @@ func (c *Client) Connect() error {
|
||||||
return webEncryptionKeyErr
|
return webEncryptionKeyErr
|
||||||
}
|
}
|
||||||
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
c.updateWebEncryptionKey(webEncryptionKeyResponse.GetKey())
|
||||||
rpcPayload, receiveMessageSessionId, err := payload.ReceiveMessages(c.authData.TachyonAuthToken)
|
go c.rpc.ListenReceiveMessages()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.rpc.rpcSessionId = receiveMessageSessionId
|
|
||||||
go c.rpc.ListenReceiveMessages(rpcPayload)
|
|
||||||
c.sessionHandler.startAckInterval()
|
c.sessionHandler.startAckInterval()
|
||||||
|
|
||||||
bugleRes, bugleErr := c.IsBugleDefault()
|
bugleRes, bugleErr := c.IsBugleDefault()
|
||||||
|
@ -128,10 +126,9 @@ func (c *Client) Connect() error {
|
||||||
if sessionErr != nil {
|
if sessionErr != nil {
|
||||||
panic(sessionErr)
|
panic(sessionErr)
|
||||||
}
|
}
|
||||||
//c.Logger.Debug().Any("tachyonAuthToken", c.authData.TachyonAuthToken).Msg("Successfully connected to server")
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
pairer, err := c.NewPairer(nil, 20)
|
pairer, err := c.NewPairer(c.authData.RefreshKey, 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -141,13 +138,7 @@ func (c *Client) Connect() error {
|
||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
c.authData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
|
c.authData.TachyonAuthToken = registered.AuthKeyData.TachyonAuthToken
|
||||||
rpcPayload, receiveMessageSessionId, err := payload.ReceiveMessages(c.authData.TachyonAuthToken)
|
go c.rpc.ListenReceiveMessages()
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.rpc.rpcSessionId = receiveMessageSessionId
|
|
||||||
go c.rpc.ListenReceiveMessages(rpcPayload)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,19 +156,6 @@ func (c *Client) IsLoggedIn() bool {
|
||||||
return c.authData != nil && c.authData.DevicePair != nil
|
return c.authData != nil && c.authData.DevicePair != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) hasTachyonTokenExpired() (bool, string) {
|
|
||||||
if c.authData.TachyonAuthToken == nil || c.authData.AuthenticatedAt == nil {
|
|
||||||
return true, ""
|
|
||||||
} else {
|
|
||||||
duration := time.Since(*c.authData.AuthenticatedAt)
|
|
||||||
seconds := fmt.Sprintf("%.3f", duration.Seconds())
|
|
||||||
if duration.Microseconds() > 86400000000 {
|
|
||||||
return true, seconds
|
|
||||||
}
|
|
||||||
return false, seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Reconnect() error {
|
func (c *Client) Reconnect() error {
|
||||||
c.rpc.CloseConnection()
|
c.rpc.CloseConnection()
|
||||||
for c.rpc.conn != nil {
|
for c.rpc.conn != nil {
|
||||||
|
@ -281,21 +259,15 @@ func (c *Client) updateWebEncryptionKey(key []byte) {
|
||||||
c.authData.WebEncryptionKey = key
|
c.authData.WebEncryptionKey = key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) updateJWK(jwk *crypto.JWK) {
|
func (c *Client) updateTachyonAuthToken(t []byte, validFor int64) {
|
||||||
c.Logger.Debug().Any("jwk", jwk).Msg("Updated JWK")
|
|
||||||
c.authData.JWK = jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) updateTachyonAuthToken(t []byte) {
|
|
||||||
authenticatedAt := time.Now().UTC()
|
|
||||||
c.authData.TachyonAuthToken = t
|
c.authData.TachyonAuthToken = t
|
||||||
c.authData.AuthenticatedAt = &authenticatedAt
|
validForDuration := time.Duration(validFor) * time.Microsecond
|
||||||
c.Logger.Debug().Any("authenticatedAt", authenticatedAt).Any("tachyonAuthToken", t).Msg("Updated TachyonAuthToken")
|
if validForDuration == 0 {
|
||||||
}
|
validForDuration = 24 * time.Hour
|
||||||
|
}
|
||||||
func (c *Client) updateTTL(ttl int64) {
|
c.authData.TachyonExpiry = time.Now().UTC().Add(time.Microsecond * time.Duration(validFor))
|
||||||
c.authData.TTL = ttl
|
c.authData.TachyonTTL = validForDuration.Microseconds()
|
||||||
c.Logger.Debug().Any("ttl", ttl).Msg("Updated TTL")
|
c.Logger.Debug().Time("tachyon_expiry", c.authData.TachyonExpiry).Int64("valid_for", validFor).Msg("Updated tachyon token")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) updateDevicePair(devicePair *pblite.DevicePair) {
|
func (c *Client) updateDevicePair(devicePair *pblite.DevicePair) {
|
||||||
|
@ -327,12 +299,12 @@ func LoadAuthSession(path string) (*AuthData, error) {
|
||||||
return sessionData, nil
|
return sessionData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) RefreshAuthToken() error {
|
|
||||||
return c.refreshAuthToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) refreshAuthToken() error {
|
func (c *Client) refreshAuthToken() error {
|
||||||
jwk := c.authData.JWK
|
if c.authData.DevicePair == nil || time.Until(c.authData.TachyonExpiry) > RefreshTachyonBuffer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.Logger.Debug().Time("tachyon_expiry", c.authData.TachyonExpiry).Msg("Refreshing auth token")
|
||||||
|
jwk := c.authData.RefreshKey
|
||||||
requestID := uuid.NewString()
|
requestID := uuid.NewString()
|
||||||
timestamp := time.Now().UnixMilli() * 1000
|
timestamp := time.Now().UnixMilli() * 1000
|
||||||
|
|
||||||
|
@ -342,14 +314,23 @@ func (c *Client) refreshAuthToken() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadMessage, messageErr := payload.RegisterRefresh(sig, requestID, int64(timestamp), c.authData.DevicePair.Browser, c.authData.TachyonAuthToken)
|
payload, err := pblite.Marshal(&binary.RegisterRefreshPayload{
|
||||||
if messageErr != nil {
|
MessageAuth: &binary.AuthMessage{
|
||||||
return messageErr
|
RequestID: requestID,
|
||||||
|
TachyonAuthToken: c.authData.TachyonAuthToken,
|
||||||
|
ConfigVersion: payload.ConfigMessage,
|
||||||
|
},
|
||||||
|
CurrBrowserDevice: c.authData.DevicePair.Browser,
|
||||||
|
UnixTimestamp: timestamp,
|
||||||
|
Signature: sig,
|
||||||
|
EmptyRefreshArr: &binary.EmptyRefreshArr{EmptyArr: &binary.EmptyArr{}},
|
||||||
|
MessageType: 2, // hmm
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Logger.Info().Any("payload", string(payloadMessage)).Msg("Attempting to refresh auth token")
|
refreshResponse, requestErr := c.rpc.sendMessageRequest(util.RegisterRefreshURL, payload)
|
||||||
|
|
||||||
refreshResponse, requestErr := c.rpc.sendMessageRequest(util.RegisterRefreshURL, payloadMessage)
|
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return requestErr
|
return requestErr
|
||||||
}
|
}
|
||||||
|
@ -377,7 +358,9 @@ func (c *Client) refreshAuthToken() error {
|
||||||
return fmt.Errorf("failed to refresh auth token: something happened")
|
return fmt.Errorf("failed to refresh auth token: something happened")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.updateTachyonAuthToken(token)
|
validFor, _ := strconv.ParseInt(resp.GetTokenData().GetValidFor(), 10, 64)
|
||||||
|
|
||||||
|
c.updateTachyonAuthToken(token, validFor)
|
||||||
c.triggerEvent(events.NewAuthTokenRefreshed(token))
|
c.triggerEvent(events.NewAuthTokenRefreshed(token))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,26 +10,19 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cryptor struct {
|
type AESCTRHelper struct {
|
||||||
AESKey []byte `json:"aes_key"`
|
AESKey []byte `json:"aes_key"`
|
||||||
HMACKey []byte `json:"hmac_key"`
|
HMACKey []byte `json:"hmac_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCryptor(aesKey []byte, hmacKey []byte) *Cryptor {
|
func NewAESCTRHelper() *AESCTRHelper {
|
||||||
if aesKey != nil && hmacKey != nil {
|
return &AESCTRHelper{
|
||||||
return &Cryptor{
|
AESKey: GenerateKey(32),
|
||||||
AESKey: aesKey,
|
HMACKey: GenerateKey(32),
|
||||||
HMACKey: hmacKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aesKey, hmacKey = GenerateKeys()
|
|
||||||
return &Cryptor{
|
|
||||||
AESKey: aesKey,
|
|
||||||
HMACKey: hmacKey,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cryptor) Encrypt(plaintext []byte) ([]byte, error) {
|
func (c *AESCTRHelper) Encrypt(plaintext []byte) ([]byte, error) {
|
||||||
iv := make([]byte, aes.BlockSize)
|
iv := make([]byte, aes.BlockSize)
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -40,7 +33,7 @@ func (c *Cryptor) Encrypt(plaintext []byte) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext := make([]byte, len(plaintext))
|
ciphertext := make([]byte, len(plaintext), len(plaintext)+len(iv)+32)
|
||||||
stream := cipher.NewCTR(block, iv)
|
stream := cipher.NewCTR(block, iv)
|
||||||
stream.XORKeyStream(ciphertext, plaintext)
|
stream.XORKeyStream(ciphertext, plaintext)
|
||||||
|
|
||||||
|
@ -48,14 +41,12 @@ func (c *Cryptor) Encrypt(plaintext []byte) ([]byte, error) {
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, c.HMACKey)
|
mac := hmac.New(sha256.New, c.HMACKey)
|
||||||
mac.Write(ciphertext)
|
mac.Write(ciphertext)
|
||||||
hmac := mac.Sum(nil)
|
ciphertext = append(ciphertext, mac.Sum(nil)...)
|
||||||
|
|
||||||
ciphertext = append(ciphertext, hmac...)
|
|
||||||
|
|
||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cryptor) Decrypt(encryptedData []byte) ([]byte, error) {
|
func (c *AESCTRHelper) Decrypt(encryptedData []byte) ([]byte, error) {
|
||||||
if len(encryptedData) < 48 {
|
if len(encryptedData) < 48 {
|
||||||
return nil, errors.New("input data is too short")
|
return nil, errors.New("input data is too short")
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,10 @@ func (t *JWK) GetPublicKey() *ecdsa.PublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateECDSAKey generates a new ECDSA private key with P-256 curve
|
// GenerateECDSAKey generates a new ECDSA private key with P-256 curve
|
||||||
func GenerateECDSAKey() (*JWK, error) {
|
func GenerateECDSAKey() *JWK {
|
||||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(fmt.Errorf("failed to generate ecdsa key: %w", err))
|
||||||
}
|
}
|
||||||
return &JWK{
|
return &JWK{
|
||||||
KeyType: "EC",
|
KeyType: "EC",
|
||||||
|
@ -63,5 +63,5 @@ func GenerateECDSAKey() (*JWK, error) {
|
||||||
D: privKey.D.Bytes(),
|
D: privKey.D.Bytes(),
|
||||||
X: privKey.X.Bytes(),
|
X: privKey.X.Bytes(),
|
||||||
Y: privKey.Y.Bytes(),
|
Y: privKey.Y.Bytes(),
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,25 +2,14 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKey(length int) ([]byte, error) {
|
func GenerateKey(length int) []byte {
|
||||||
key := make([]byte, length)
|
key := make([]byte, length)
|
||||||
_, err := rand.Read(key)
|
_, err := rand.Read(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
panic(fmt.Errorf("failed to read random bytes: %w", err))
|
||||||
}
|
}
|
||||||
return key, nil
|
return key
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateKeys() ([]byte, []byte) {
|
|
||||||
key, err := GenerateKey(32)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
key2, err2 := GenerateKey(32)
|
|
||||||
if err2 != nil {
|
|
||||||
panic(err2)
|
|
||||||
}
|
|
||||||
return key, key2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (r *RPC) deduplicateUpdate(response *pblite.Response) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RPC) HandleRPCMsg(msg *binary.InternalMessage) {
|
func (r *RPC) HandleRPCMsg(msg *binary.InternalMessage) {
|
||||||
response, decodeErr := pblite.DecryptInternalMessage(msg, r.client.authData.Cryptor)
|
response, decodeErr := pblite.DecryptInternalMessage(msg, r.client.authData.RequestCrypto)
|
||||||
if decodeErr != nil {
|
if decodeErr != nil {
|
||||||
r.client.Logger.Error().Err(decodeErr).Msg("rpc decrypt msg err")
|
r.client.Logger.Error().Err(decodeErr).Msg("rpc decrypt msg err")
|
||||||
return
|
return
|
||||||
|
|
|
@ -82,10 +82,7 @@ func (c *Client) UploadMedia(data []byte, fileName, mime string) (*binary.MediaC
|
||||||
if mediaType.Type == 0 {
|
if mediaType.Type == 0 {
|
||||||
mediaType = MimeToMediaType[strings.Split(mime, "/")[0]]
|
mediaType = MimeToMediaType[strings.Split(mime, "/")[0]]
|
||||||
}
|
}
|
||||||
decryptionKey, err := crypto.GenerateKey(32)
|
decryptionKey := crypto.GenerateKey(32)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cryptor, err := crypto.NewImageCryptor(decryptionKey)
|
cryptor, err := crypto.NewImageCryptor(decryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package libgm
|
package libgm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -23,15 +24,6 @@ type Pairer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) NewPairer(keyData *crypto.JWK, refreshQrCodeTime int) (*Pairer, error) {
|
func (c *Client) NewPairer(keyData *crypto.JWK, refreshQrCodeTime int) (*Pairer, error) {
|
||||||
if keyData == nil {
|
|
||||||
var err error
|
|
||||||
keyData, err = crypto.GenerateECDSAKey()
|
|
||||||
c.updateJWK(keyData)
|
|
||||||
if err != nil {
|
|
||||||
c.Logger.Error().Any("data", keyData).Msg(err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p := &Pairer{
|
p := &Pairer{
|
||||||
client: c,
|
client: c,
|
||||||
KeyData: keyData,
|
KeyData: keyData,
|
||||||
|
@ -42,7 +34,27 @@ func (c *Client) NewPairer(keyData *crypto.JWK, refreshQrCodeTime int) (*Pairer,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pairer) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error) {
|
func (p *Pairer) RegisterPhoneRelay() (*binary.RegisterPhoneRelayResponse, error) {
|
||||||
body, _, err := payload.RegisterPhoneRelay(p.KeyData)
|
key, err := x509.MarshalPKIXPublicKey(p.KeyData.GetPublicKey())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := proto.Marshal(&binary.AuthenticationContainer{
|
||||||
|
AuthMessage: &binary.AuthMessage{
|
||||||
|
RequestID: uuid.NewString(),
|
||||||
|
Network: &payload.Network,
|
||||||
|
ConfigVersion: payload.ConfigMessage,
|
||||||
|
},
|
||||||
|
BrowserDetails: payload.BrowserDetailsMessage,
|
||||||
|
Data: &binary.AuthenticationContainer_KeyData{
|
||||||
|
KeyData: &binary.KeyData{
|
||||||
|
EcdsaKeys: &binary.ECDSAKeys{
|
||||||
|
Field1: 2,
|
||||||
|
EncryptedKeys: key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.client.Logger.Err(err)
|
p.client.Logger.Err(err)
|
||||||
return &binary.RegisterPhoneRelayResponse{}, err
|
return &binary.RegisterPhoneRelayResponse{}, err
|
||||||
|
|
|
@ -39,8 +39,7 @@ func (c *Client) NewDevicePair(mobile, browser *binary.Device) *pblite.DevicePai
|
||||||
func (c *Client) pairCallback(data *binary.PairedData) error {
|
func (c *Client) pairCallback(data *binary.PairedData) error {
|
||||||
|
|
||||||
tokenData := data.GetTokenData()
|
tokenData := data.GetTokenData()
|
||||||
c.updateTachyonAuthToken(tokenData.GetTachyonAuthToken())
|
c.updateTachyonAuthToken(tokenData.GetTachyonAuthToken(), tokenData.GetTTL())
|
||||||
c.updateTTL(tokenData.GetTTL())
|
|
||||||
|
|
||||||
devicePair := c.NewDevicePair(data.Mobile, data.Browser)
|
devicePair := c.NewDevicePair(data.Mobile, data.Browser)
|
||||||
c.updateDevicePair(devicePair)
|
c.updateDevicePair(devicePair)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package payload
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReceiveMessages(rpcKey []byte) ([]byte, string, error) {
|
|
||||||
payload := &binary.ReceiveMessagesRequest{
|
|
||||||
Auth: &binary.AuthMessage{
|
|
||||||
RequestID: uuid.New().String(),
|
|
||||||
TachyonAuthToken: rpcKey,
|
|
||||||
ConfigVersion: ConfigMessage,
|
|
||||||
},
|
|
||||||
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject2{
|
|
||||||
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject1{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jsonData, err := pblite.Marshal(payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
return jsonData, payload.Auth.RequestID, nil
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package payload
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterPhoneRelay(jwk *crypto.JWK) ([]byte, *binary.AuthenticationContainer, error) {
|
|
||||||
key, err := x509.MarshalPKIXPublicKey(jwk.GetPublicKey())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payloadData := &binary.AuthenticationContainer{
|
|
||||||
AuthMessage: &binary.AuthMessage{
|
|
||||||
RequestID: uuid.NewString(),
|
|
||||||
Network: &Network,
|
|
||||||
ConfigVersion: ConfigMessage,
|
|
||||||
},
|
|
||||||
BrowserDetails: BrowserDetailsMessage,
|
|
||||||
Data: &binary.AuthenticationContainer_KeyData{
|
|
||||||
KeyData: &binary.KeyData{
|
|
||||||
EcdsaKeys: &binary.ECDSAKeys{
|
|
||||||
Field1: 2,
|
|
||||||
EncryptedKeys: key,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
encoded, err4 := proto.Marshal(payloadData)
|
|
||||||
if err4 != nil {
|
|
||||||
return nil, payloadData, err4
|
|
||||||
}
|
|
||||||
return encoded, payloadData, nil
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package payload
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterRefresh(sig []byte, requestID string, timestamp int64, browser *binary.Device, tachyonAuthToken []byte) ([]byte, error) {
|
|
||||||
payload := &binary.RegisterRefreshPayload{
|
|
||||||
MessageAuth: &binary.AuthMessage{
|
|
||||||
RequestID: requestID,
|
|
||||||
TachyonAuthToken: tachyonAuthToken,
|
|
||||||
ConfigVersion: ConfigMessage,
|
|
||||||
},
|
|
||||||
CurrBrowserDevice: browser,
|
|
||||||
UnixTimestamp: timestamp,
|
|
||||||
Signature: sig,
|
|
||||||
EmptyRefreshArr: &binary.EmptyRefreshArr{EmptyArr: &binary.EmptyArr{}},
|
|
||||||
MessageType: 2, // hmm
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonMessage, serializeErr := pblite.Marshal(payload)
|
|
||||||
if serializeErr != nil {
|
|
||||||
return nil, serializeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonMessage, nil
|
|
||||||
}
|
|
|
@ -91,7 +91,7 @@ func (sm *SendMessageBuilder) SetTTL(ttl int64) *SendMessageBuilder {
|
||||||
return sm
|
return sm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *SendMessageBuilder) SetEncryptedProtoMessage(message proto.Message, cryptor *crypto.Cryptor) *SendMessageBuilder {
|
func (sm *SendMessageBuilder) SetEncryptedProtoMessage(message proto.Message, cryptor *crypto.AESCTRHelper) *SendMessageBuilder {
|
||||||
plaintextBytes, err := proto.Marshal(message)
|
plaintextBytes, err := proto.Marshal(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sm.err = err
|
sm.err = err
|
||||||
|
|
|
@ -39,7 +39,7 @@ type Response struct {
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptInternalMessage(internalMessage *binary.InternalMessage, cryptor *crypto.Cryptor) (*Response, error) {
|
func DecryptInternalMessage(internalMessage *binary.InternalMessage, cryptor *crypto.AESCTRHelper) (*Response, error) {
|
||||||
var resp *Response
|
var resp *Response
|
||||||
switch internalMessage.Data.BugleRoute {
|
switch internalMessage.Data.BugleRoute {
|
||||||
case binary.BugleRoute_PairEvent:
|
case binary.BugleRoute_PairEvent:
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
func (p *Pairer) GenerateQRCodeData() (string, error) {
|
func (p *Pairer) GenerateQRCodeData() (string, error) {
|
||||||
urlData := &binary.URLData{
|
urlData := &binary.URLData{
|
||||||
PairingKey: p.pairingKey,
|
PairingKey: p.pairingKey,
|
||||||
AESKey: p.client.authData.Cryptor.AESKey,
|
AESKey: p.client.authData.RequestCrypto.AESKey,
|
||||||
HMACKey: p.client.authData.Cryptor.HMACKey,
|
HMACKey: p.client.authData.RequestCrypto.HMACKey,
|
||||||
}
|
}
|
||||||
encodedURLData, err := proto.Marshal(urlData)
|
encodedURLData, err := proto.Marshal(urlData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
24
libgm/rpc.go
24
libgm/rpc.go
|
@ -11,9 +11,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||||
|
"go.mau.fi/mautrix-gmessages/libgm/payload"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
|
@ -25,7 +27,6 @@ type RPC struct {
|
||||||
http *http.Client
|
http *http.Client
|
||||||
conn io.ReadCloser
|
conn io.ReadCloser
|
||||||
stopping bool
|
stopping bool
|
||||||
rpcSessionId string
|
|
||||||
listenID int
|
listenID int
|
||||||
|
|
||||||
skipCount int
|
skipCount int
|
||||||
|
@ -34,22 +35,33 @@ type RPC struct {
|
||||||
recentUpdatesPtr int
|
recentUpdatesPtr int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RPC) ListenReceiveMessages(payload []byte) {
|
func (r *RPC) ListenReceiveMessages() {
|
||||||
r.listenID++
|
r.listenID++
|
||||||
listenID := r.listenID
|
listenID := r.listenID
|
||||||
errored := true
|
errored := true
|
||||||
|
listenReqID := uuid.NewString()
|
||||||
for r.listenID == listenID {
|
for r.listenID == listenID {
|
||||||
if r.client.authData.DevicePair != nil && r.client.authData.AuthenticatedAt.Add(20*time.Hour).Before(time.Now()) {
|
|
||||||
r.client.Logger.Debug().Msg("Refreshing auth token before starting new long-polling request")
|
|
||||||
err := r.client.refreshAuthToken()
|
err := r.client.refreshAuthToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.client.Logger.Err(err).Msg("Error refreshing auth token")
|
r.client.Logger.Err(err).Msg("Error refreshing auth token")
|
||||||
r.client.triggerEvent(&events.ListenFatalError{Error: fmt.Errorf("failed to refresh auth token: %w", err)})
|
r.client.triggerEvent(&events.ListenFatalError{Error: fmt.Errorf("failed to refresh auth token: %w", err)})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
r.client.Logger.Debug().Msg("Starting new long-polling request")
|
r.client.Logger.Debug().Msg("Starting new long-polling request")
|
||||||
req, err := http.NewRequest("POST", util.ReceiveMessagesURL, bytes.NewReader(payload))
|
receivePayload, err := pblite.Marshal(&binary.ReceiveMessagesRequest{
|
||||||
|
Auth: &binary.AuthMessage{
|
||||||
|
RequestID: listenReqID,
|
||||||
|
TachyonAuthToken: r.client.authData.TachyonAuthToken,
|
||||||
|
ConfigVersion: payload.ConfigMessage,
|
||||||
|
},
|
||||||
|
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject2{
|
||||||
|
Unknown: &binary.ReceiveMessagesRequest_UnknownEmptyObject1{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error marshaling request: %v", err))
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, util.ReceiveMessagesURL, bytes.NewReader(receivePayload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("Error creating request: %v", err))
|
panic(fmt.Errorf("Error creating request: %v", err))
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,11 +91,11 @@ func (s *SessionHandler) buildMessage(actionType binary.ActionType, encryptedDat
|
||||||
tmpMessage := payload.NewSendMessageBuilder(token, pairedDevice, requestID, sessionId).SetRoute(routeInfo.Action).SetSessionId(s.sessionID)
|
tmpMessage := payload.NewSendMessageBuilder(token, pairedDevice, requestID, sessionId).SetRoute(routeInfo.Action).SetSessionId(s.sessionID)
|
||||||
|
|
||||||
if encryptedData != nil {
|
if encryptedData != nil {
|
||||||
tmpMessage.SetEncryptedProtoMessage(encryptedData, s.client.authData.Cryptor)
|
tmpMessage.SetEncryptedProtoMessage(encryptedData, s.client.authData.RequestCrypto)
|
||||||
}
|
}
|
||||||
|
|
||||||
if routeInfo.UseTTL {
|
if routeInfo.UseTTL {
|
||||||
tmpMessage.SetTTL(s.client.authData.TTL)
|
tmpMessage.SetTTL(s.client.authData.TachyonTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
message, buildErr := tmpMessage.Build()
|
message, buildErr := tmpMessage.Build()
|
||||||
|
|
|
@ -55,7 +55,6 @@ func (c *Client) handleClientReady(newSessionId string) {
|
||||||
if convErr != nil {
|
if convErr != nil {
|
||||||
panic(convErr)
|
panic(convErr)
|
||||||
}
|
}
|
||||||
c.Logger.Debug().Any("conversations", conversations).Msg("got conversations")
|
|
||||||
notifyErr := c.NotifyDittoActivity()
|
notifyErr := c.NotifyDittoActivity()
|
||||||
if notifyErr != nil {
|
if notifyErr != nil {
|
||||||
panic(notifyErr)
|
panic(notifyErr)
|
||||||
|
|
5
user.go
5
user.go
|
@ -42,7 +42,6 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/database"
|
"go.mau.fi/mautrix-gmessages/database"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm"
|
"go.mau.fi/mautrix-gmessages/libgm"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
"go.mau.fi/mautrix-gmessages/libgm/binary"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/crypto"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/events"
|
"go.mau.fi/mautrix-gmessages/libgm/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -393,9 +392,7 @@ var ErrAlreadyLoggedIn = errors.New("already logged in")
|
||||||
|
|
||||||
func (user *User) createClient() {
|
func (user *User) createClient() {
|
||||||
if user.Session == nil {
|
if user.Session == nil {
|
||||||
user.Session = &libgm.AuthData{
|
user.Session = libgm.NewAuthData()
|
||||||
Cryptor: crypto.NewCryptor(nil, nil),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
user.Client = libgm.NewClient(user.Session, user.zlog.With().Str("component", "libgm").Logger())
|
user.Client = libgm.NewClient(user.Session, user.zlog.With().Str("component", "libgm").Logger())
|
||||||
user.Client.SetEventHandler(user.syncHandleEvent)
|
user.Client.SetEventHandler(user.syncHandleEvent)
|
||||||
|
|
Loading…
Reference in a new issue