summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow/pair.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/pair.go')
-rw-r--r--vendor/go.mau.fi/whatsmeow/pair.go244
1 files changed, 244 insertions, 0 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/pair.go b/vendor/go.mau.fi/whatsmeow/pair.go
new file mode 100644
index 00000000..389d6f52
--- /dev/null
+++ b/vendor/go.mau.fi/whatsmeow/pair.go
@@ -0,0 +1,244 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package whatsmeow
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "strings"
+ "time"
+
+ "google.golang.org/protobuf/proto"
+
+ "go.mau.fi/libsignal/ecc"
+
+ waBinary "go.mau.fi/whatsmeow/binary"
+ waProto "go.mau.fi/whatsmeow/binary/proto"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/types/events"
+ "go.mau.fi/whatsmeow/util/keys"
+)
+
+const qrScanTimeout = 30 * time.Second
+
+func (cli *Client) handleIQ(node *waBinary.Node) {
+ children := node.GetChildren()
+ if len(children) != 1 || node.Attrs["from"] != types.ServerJID {
+ return
+ }
+ switch children[0].Tag {
+ case "pair-device":
+ cli.handlePairDevice(node)
+ case "pair-success":
+ cli.handlePairSuccess(node)
+ }
+}
+
+func (cli *Client) handlePairDevice(node *waBinary.Node) {
+ pairDevice := node.GetChildByTag("pair-device")
+ err := cli.sendNode(waBinary.Node{
+ Tag: "iq",
+ Attrs: waBinary.Attrs{
+ "to": node.Attrs["from"],
+ "id": node.Attrs["id"],
+ "type": "result",
+ },
+ })
+ if err != nil {
+ cli.Log.Warnf("Failed to send acknowledgement for pair-device request: %v", err)
+ }
+
+ evt := &events.QR{Codes: make([]string, 0, len(pairDevice.GetChildren()))}
+ for i, child := range pairDevice.GetChildren() {
+ if child.Tag != "ref" {
+ cli.Log.Warnf("pair-device node contains unexpected child tag %s at index %d", child.Tag, i)
+ continue
+ }
+ content, ok := child.Content.([]byte)
+ if !ok {
+ cli.Log.Warnf("pair-device node contains unexpected child content type %T at index %d", child, i)
+ continue
+ }
+ evt.Codes = append(evt.Codes, cli.makeQRData(string(content)))
+ }
+
+ cli.dispatchEvent(evt)
+}
+
+func (cli *Client) makeQRData(ref string) string {
+ noise := base64.StdEncoding.EncodeToString(cli.Store.NoiseKey.Pub[:])
+ identity := base64.StdEncoding.EncodeToString(cli.Store.IdentityKey.Pub[:])
+ adv := base64.StdEncoding.EncodeToString(cli.Store.AdvSecretKey)
+ return strings.Join([]string{ref, noise, identity, adv}, ",")
+}
+
+func (cli *Client) handlePairSuccess(node *waBinary.Node) {
+ id := node.Attrs["id"].(string)
+ pairSuccess := node.GetChildByTag("pair-success")
+
+ deviceIdentityBytes, _ := pairSuccess.GetChildByTag("device-identity").Content.([]byte)
+ businessName, _ := pairSuccess.GetChildByTag("biz").Attrs["name"].(string)
+ jid, _ := pairSuccess.GetChildByTag("device").Attrs["jid"].(types.JID)
+ platform, _ := pairSuccess.GetChildByTag("platform").Attrs["name"].(string)
+
+ go func() {
+ err := cli.handlePair(deviceIdentityBytes, id, businessName, platform, jid)
+ if err != nil {
+ cli.Log.Errorf("Failed to pair device: %v", err)
+ cli.Disconnect()
+ cli.dispatchEvent(&events.PairError{ID: jid, BusinessName: businessName, Platform: platform, Error: err})
+ } else {
+ cli.Log.Infof("Successfully paired %s", cli.Store.ID)
+ cli.dispatchEvent(&events.PairSuccess{ID: jid, BusinessName: businessName, Platform: platform})
+ }
+ }()
+}
+
+func (cli *Client) handlePair(deviceIdentityBytes []byte, reqID, businessName, platform string, jid types.JID) error {
+ var deviceIdentityContainer waProto.ADVSignedDeviceIdentityHMAC
+ err := proto.Unmarshal(deviceIdentityBytes, &deviceIdentityContainer)
+ if err != nil {
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to parse device identity container in pair success message: %w", err)
+ }
+
+ h := hmac.New(sha256.New, cli.Store.AdvSecretKey)
+ h.Write(deviceIdentityContainer.Details)
+ if !bytes.Equal(h.Sum(nil), deviceIdentityContainer.Hmac) {
+ cli.Log.Warnf("Invalid HMAC from pair success message")
+ cli.sendIQError(reqID, 401, "not-authorized")
+ return fmt.Errorf("invalid device identity HMAC in pair success message")
+ }
+
+ var deviceIdentity waProto.ADVSignedDeviceIdentity
+ err = proto.Unmarshal(deviceIdentityContainer.Details, &deviceIdentity)
+ if err != nil {
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to parse signed device identity in pair success message: %w", err)
+ }
+
+ if !verifyDeviceIdentityAccountSignature(&deviceIdentity, cli.Store.IdentityKey) {
+ cli.sendIQError(reqID, 401, "not-authorized")
+ return fmt.Errorf("invalid device signature in pair success message")
+ }
+
+ deviceIdentity.DeviceSignature = generateDeviceSignature(&deviceIdentity, cli.Store.IdentityKey)[:]
+
+ var deviceIdentityDetails waProto.ADVDeviceIdentity
+ err = proto.Unmarshal(deviceIdentity.Details, &deviceIdentityDetails)
+ if err != nil {
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to parse device identity details in pair success message: %w", err)
+ }
+
+ mainDeviceJID := jid
+ mainDeviceJID.Device = 0
+ mainDeviceIdentity := *(*[32]byte)(deviceIdentity.AccountSignatureKey)
+ deviceIdentity.AccountSignatureKey = nil
+
+ cli.Store.Account = proto.Clone(&deviceIdentity).(*waProto.ADVSignedDeviceIdentity)
+
+ selfSignedDeviceIdentity, err := proto.Marshal(&deviceIdentity)
+ if err != nil {
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to marshal self-signed device identity: %w", err)
+ }
+
+ cli.Store.ID = &jid
+ cli.Store.BusinessName = businessName
+ cli.Store.Platform = platform
+ err = cli.Store.Save()
+ if err != nil {
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to save device store: %w", err)
+ }
+ err = cli.Store.Identities.PutIdentity(mainDeviceJID.SignalAddress().String(), mainDeviceIdentity)
+ if err != nil {
+ _ = cli.Store.Delete()
+ cli.sendIQError(reqID, 500, "internal-error")
+ return fmt.Errorf("failed to store main device identity: %w", err)
+ }
+
+ // Expect a disconnect after this and don't dispatch the usual Disconnected event
+ cli.expectDisconnect()
+
+ err = cli.sendNode(waBinary.Node{
+ Tag: "iq",
+ Attrs: waBinary.Attrs{
+ "to": types.ServerJID,
+ "type": "result",
+ "id": reqID,
+ },
+ Content: []waBinary.Node{{
+ Tag: "pair-device-sign",
+ Content: []waBinary.Node{{
+ Tag: "device-identity",
+ Attrs: waBinary.Attrs{
+ "key-index": deviceIdentityDetails.GetKeyIndex(),
+ },
+ Content: selfSignedDeviceIdentity,
+ }},
+ }},
+ })
+ if err != nil {
+ _ = cli.Store.Delete()
+ return fmt.Errorf("failed to send pairing confirmation: %w", err)
+ }
+ return nil
+}
+
+func concatBytes(data ...[]byte) []byte {
+ length := 0
+ for _, item := range data {
+ length += len(item)
+ }
+ output := make([]byte, length)
+ ptr := 0
+ for _, item := range data {
+ ptr += copy(output[ptr:ptr+len(item)], item)
+ }
+ return output
+}
+
+func verifyDeviceIdentityAccountSignature(deviceIdentity *waProto.ADVSignedDeviceIdentity, ikp *keys.KeyPair) bool {
+ if len(deviceIdentity.AccountSignatureKey) != 32 || len(deviceIdentity.AccountSignature) != 64 {
+ return false
+ }
+
+ signatureKey := ecc.NewDjbECPublicKey(*(*[32]byte)(deviceIdentity.AccountSignatureKey))
+ signature := *(*[64]byte)(deviceIdentity.AccountSignature)
+
+ message := concatBytes([]byte{6, 0}, deviceIdentity.Details, ikp.Pub[:])
+ return ecc.VerifySignature(signatureKey, message, signature)
+}
+
+func generateDeviceSignature(deviceIdentity *waProto.ADVSignedDeviceIdentity, ikp *keys.KeyPair) *[64]byte {
+ message := concatBytes([]byte{6, 1}, deviceIdentity.Details, ikp.Pub[:], deviceIdentity.AccountSignatureKey)
+ sig := ecc.CalculateSignature(ecc.NewDjbECPrivateKey(*ikp.Priv), message)
+ return &sig
+}
+
+func (cli *Client) sendIQError(id string, code int, text string) waBinary.Node {
+ return waBinary.Node{
+ Tag: "iq",
+ Attrs: waBinary.Attrs{
+ "to": types.ServerJID,
+ "type": "error",
+ "id": id,
+ },
+ Content: []waBinary.Node{{
+ Tag: "error",
+ Attrs: waBinary.Attrs{
+ "code": code,
+ "text": text,
+ },
+ }},
+ }
+}