diff options
Diffstat (limited to 'vendor/github.com/matterbridge/go-whatsapp/session.go')
-rw-r--r-- | vendor/github.com/matterbridge/go-whatsapp/session.go | 389 |
1 files changed, 0 insertions, 389 deletions
diff --git a/vendor/github.com/matterbridge/go-whatsapp/session.go b/vendor/github.com/matterbridge/go-whatsapp/session.go deleted file mode 100644 index 2dd8d582..00000000 --- a/vendor/github.com/matterbridge/go-whatsapp/session.go +++ /dev/null @@ -1,389 +0,0 @@ -package whatsapp - -import ( - "crypto/hmac" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "time" - - "github.com/matterbridge/go-whatsapp/crypto/cbc" - "github.com/matterbridge/go-whatsapp/crypto/curve25519" - "github.com/matterbridge/go-whatsapp/crypto/hkdf" -) - -/* -Session contains session individual information. To be able to resume the connection without scanning the qr code -every time you should save the Session returned by Login and use RestoreSession the next time you want to login. -Every successful created connection returns a new Session. The Session(ClientToken, ServerToken) is altered after -every re-login and should be saved every time. -*/ -type Session struct { - ClientId string - ClientToken string - ServerToken string - EncKey []byte - MacKey []byte - Wid string -} - -type Info struct { - Battery int - Platform string - Connected bool - Pushname string - Wid string - Lc string - Phone *PhoneInfo - Plugged bool - Tos int - Lg string - Is24h bool -} - -type PhoneInfo struct { - Mcc string - Mnc string - OsVersion string - DeviceManufacturer string - DeviceModel string - OsBuildNumber string - WaVersion string -} - -func newInfoFromReq(info map[string]interface{}) *Info { - phoneInfo := info["phone"].(map[string]interface{}) - - ret := &Info{ - Battery: int(info["battery"].(float64)), - Platform: info["platform"].(string), - Connected: info["connected"].(bool), - Pushname: info["pushname"].(string), - Wid: info["wid"].(string), - Lc: info["lc"].(string), - Phone: &PhoneInfo{ - phoneInfo["mcc"].(string), - phoneInfo["mnc"].(string), - phoneInfo["os_version"].(string), - phoneInfo["device_manufacturer"].(string), - phoneInfo["device_model"].(string), - phoneInfo["os_build_number"].(string), - phoneInfo["wa_version"].(string), - }, - Plugged: info["plugged"].(bool), - Lg: info["lg"].(string), - Tos: int(info["tos"].(float64)), - } - - if is24h, ok := info["is24h"]; ok { - ret.Is24h = is24h.(bool) - } - - return ret -} - -/* -SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the -WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible. -*/ -func (wac *Conn) SetClientName(long, short string) error { - if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) { - return fmt.Errorf("cannot change client name after logging in") - } - wac.longClientName, wac.shortClientName = long, short - return nil -} - -/* -Login is the function that creates a new whatsapp session and logs you in. If you do not want to scan the qr code -every time, you should save the returned session and use RestoreSession the next time. Login takes a writable channel -as an parameter. This channel is used to push the data represented by the qr code back to the user. The received data -should be displayed as an qr code in a way you prefer. To print a qr code to console you can use: -github.com/Baozisoftware/qrcode-terminal-go Example login procedure: - wac, err := whatsapp.NewConn(5 * time.Second) - if err != nil { - panic(err) - } - - qr := make(chan string) - go func() { - terminal := qrcodeTerminal.New() - terminal.Get(<-qr).Print() - }() - - session, err := wac.Login(qr) - if err != nil { - fmt.Fprintf(os.Stderr, "error during login: %v\n", err) - } - fmt.Printf("login successful, session: %v\n", session) -*/ -func (wac *Conn) Login(qrChan chan<- string) (Session, error) { - session := Session{} - - if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) { - return session, fmt.Errorf("already logged in") - } - - clientId := make([]byte, 16) - _, err := rand.Read(clientId) - if err != nil { - return session, fmt.Errorf("error creating random ClientId: %v", err) - } - - session.ClientId = base64.StdEncoding.EncodeToString(clientId) - //oldVersion=8691 - login := []interface{}{"admin", "init", []int{0, 3, 225}, []string{wac.longClientName, wac.shortClientName}, session.ClientId, true} - loginChan, err := wac.write(login) - if err != nil { - return session, fmt.Errorf("error writing login: %v\n", err) - } - - var r string - select { - case r = <-loginChan: - case <-time.After(wac.msgTimeout): - return session, fmt.Errorf("login connection timed out") - } - - var resp map[string]interface{} - if err = json.Unmarshal([]byte(r), &resp); err != nil { - return session, fmt.Errorf("error decoding login resp: %v\n", err) - } - - ref := resp["ref"].(string) - - priv, pub, err := curve25519.GenerateKey() - if err != nil { - return session, fmt.Errorf("error generating keys: %v\n", err) - } - - //listener for Login response - messageTag := "s1" - wac.listener[messageTag] = make(chan string, 1) - - qrChan <- fmt.Sprintf("%v,%v,%v", ref, base64.StdEncoding.EncodeToString(pub[:]), session.ClientId) - - var resp2 []interface{} - select { - case r1 := <-wac.listener[messageTag]: - if err := json.Unmarshal([]byte(r1), &resp2); err != nil { - return session, fmt.Errorf("error decoding qr code resp: %v", err) - } - case <-time.After(time.Duration(resp["ttl"].(float64)) * time.Millisecond): - return session, fmt.Errorf("qr code scan timed out") - } - - info := resp2[1].(map[string]interface{}) - - wac.Info = newInfoFromReq(info) - - session.ClientToken = info["clientToken"].(string) - session.ServerToken = info["serverToken"].(string) - session.Wid = info["wid"].(string) - s := info["secret"].(string) - decodedSecret, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return session, fmt.Errorf("error decoding secret: %v", err) - } - - var pubKey [32]byte - copy(pubKey[:], decodedSecret[:32]) - - sharedSecret := curve25519.GenerateSharedSecret(*priv, pubKey) - - hash := sha256.New - - nullKey := make([]byte, 32) - h := hmac.New(hash, nullKey) - h.Write(sharedSecret) - - sharedSecretExtended, err := hkdf.Expand(h.Sum(nil), 80, "") - if err != nil { - return session, fmt.Errorf("hkdf error: %v", err) - } - - //login validation - checkSecret := make([]byte, 112) - copy(checkSecret[:32], decodedSecret[:32]) - copy(checkSecret[32:], decodedSecret[64:]) - h2 := hmac.New(hash, sharedSecretExtended[32:64]) - h2.Write(checkSecret) - if !hmac.Equal(h2.Sum(nil), decodedSecret[32:64]) { - return session, fmt.Errorf("abort login") - } - - keysEncrypted := make([]byte, 96) - copy(keysEncrypted[:16], sharedSecretExtended[64:]) - copy(keysEncrypted[16:], decodedSecret[64:]) - - keyDecrypted, err := cbc.Decrypt(sharedSecretExtended[:32], nil, keysEncrypted) - if err != nil { - return session, fmt.Errorf("error decryptAes: %v", err) - } - - session.EncKey = keyDecrypted[:32] - session.MacKey = keyDecrypted[32:64] - wac.session = &session - - return session, nil -} - -/* -RestoreSession is the function that restores a given session. It will try to reestablish the connection to the -WhatsAppWeb servers with the provided session. If it succeeds it will return a new session. This new session has to be -saved because the Client and Server-Token will change after every login. Logging in with old tokens is possible, but not -suggested. If so, a challenge has to be resolved which is just another possible point of failure. -*/ -func (wac *Conn) RestoreSession(session Session) (Session, error) { - if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) { - return Session{}, fmt.Errorf("already logged in") - } - - wac.session = &session - - //listener for Conn or challenge; s1 is not allowed to drop - wac.listener["s1"] = make(chan string, 1) - - //admin init - init := []interface{}{"admin", "init", []int{0, 3, 225}, []string{wac.longClientName, wac.shortClientName}, session.ClientId, true} - initChan, err := wac.write(init) - if err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error writing admin init: %v\n", err) - } - - //admin login with takeover - login := []interface{}{"admin", "login", session.ClientToken, session.ServerToken, session.ClientId, "takeover"} - loginChan, err := wac.write(login) - if err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error writing admin login: %v\n", err) - } - - select { - case r := <-initChan: - var resp map[string]interface{} - if err = json.Unmarshal([]byte(r), &resp); err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error decoding login connResp: %v\n", err) - } - - if int(resp["status"].(float64)) != 200 { - wac.session = nil - return Session{}, fmt.Errorf("init responded with %d", resp["status"]) - } - case <-time.After(wac.msgTimeout): - wac.session = nil - return Session{}, fmt.Errorf("restore session init timed out") - } - - //wait for s1 - var connResp []interface{} - select { - case r1 := <-wac.listener["s1"]: - if err := json.Unmarshal([]byte(r1), &connResp); err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error decoding s1 message: %v\n", err) - } - case <-time.After(wac.msgTimeout): - wac.session = nil - return Session{}, fmt.Errorf("restore session connection timed out") - } - - //check if challenge is present - if len(connResp) == 2 && connResp[0] == "Cmd" && connResp[1].(map[string]interface{})["type"] == "challenge" { - wac.listener["s2"] = make(chan string, 1) - - if err := wac.resolveChallenge(connResp[1].(map[string]interface{})["challenge"].(string)); err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error resolving challenge: %v\n", err) - } - - select { - case r := <-wac.listener["s2"]: - if err := json.Unmarshal([]byte(r), &connResp); err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error decoding s2 message: %v\n", err) - } - case <-time.After(wac.msgTimeout): - wac.session = nil - return Session{}, fmt.Errorf("restore session challenge timed out") - } - } - - //check for login 200 --> login success - select { - case r := <-loginChan: - var resp map[string]interface{} - if err = json.Unmarshal([]byte(r), &resp); err != nil { - wac.session = nil - return Session{}, fmt.Errorf("error decoding login connResp: %v\n", err) - } - - if int(resp["status"].(float64)) != 200 { - wac.session = nil - return Session{}, fmt.Errorf("admin login responded with %d", resp["status"]) - } - case <-time.After(wac.msgTimeout): - wac.session = nil - return Session{}, fmt.Errorf("restore session login timed out") - } - - info := connResp[1].(map[string]interface{}) - - wac.Info = newInfoFromReq(info) - - //set new tokens - session.ClientToken = info["clientToken"].(string) - session.ServerToken = info["serverToken"].(string) - session.Wid = info["wid"].(string) - - return *wac.session, nil -} - -func (wac *Conn) resolveChallenge(challenge string) error { - decoded, err := base64.StdEncoding.DecodeString(challenge) - if err != nil { - return err - } - - h2 := hmac.New(sha256.New, wac.session.MacKey) - h2.Write([]byte(decoded)) - - ch := []interface{}{"admin", "challenge", base64.StdEncoding.EncodeToString(h2.Sum(nil)), wac.session.ServerToken, wac.session.ClientId} - challengeChan, err := wac.write(ch) - if err != nil { - return fmt.Errorf("error writing challenge: %v\n", err) - } - - select { - case r := <-challengeChan: - var resp map[string]interface{} - if err := json.Unmarshal([]byte(r), &resp); err != nil { - return fmt.Errorf("error decoding login resp: %v\n", err) - } - if int(resp["status"].(float64)) != 200 { - return fmt.Errorf("challenge responded with %d\n", resp["status"]) - } - case <-time.After(wac.msgTimeout): - return fmt.Errorf("connection timed out") - } - - return nil -} - -/* -Logout is the function to logout from a WhatsApp session. Logging out means invalidating the current session. -The session can not be resumed and will disappear on your phone in the WhatsAppWeb client list. -*/ -func (wac *Conn) Logout() error { - login := []interface{}{"admin", "Conn", "disconnect"} - _, err := wac.write(login) - if err != nil { - return fmt.Errorf("error writing logout: %v\n", err) - } - - return nil -} |