pair.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright (c) 2021 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "bytes"
  9. "context"
  10. "crypto/hmac"
  11. "crypto/sha256"
  12. "encoding/base64"
  13. "fmt"
  14. "strings"
  15. "go.mau.fi/libsignal/ecc"
  16. "google.golang.org/protobuf/proto"
  17. waBinary "go.mau.fi/whatsmeow/binary"
  18. "go.mau.fi/whatsmeow/proto/waAdv"
  19. "go.mau.fi/whatsmeow/types"
  20. "go.mau.fi/whatsmeow/types/events"
  21. "go.mau.fi/whatsmeow/util/keys"
  22. )
  23. var (
  24. AdvAccountSignaturePrefix = []byte{6, 0}
  25. AdvDeviceSignaturePrefix = []byte{6, 1}
  26. AdvHostedAccountSignaturePrefix = []byte{6, 5}
  27. AdvHostedDeviceSignaturePrefix = []byte{6, 6}
  28. )
  29. func (cli *Client) handleIQ(ctx context.Context, node *waBinary.Node) {
  30. children := node.GetChildren()
  31. if len(children) != 1 || node.Attrs["from"] != types.ServerJID {
  32. return
  33. }
  34. switch children[0].Tag {
  35. case "pair-device":
  36. cli.handlePairDevice(ctx, node)
  37. case "pair-success":
  38. cli.handlePairSuccess(ctx, node)
  39. }
  40. }
  41. func (cli *Client) handlePairDevice(ctx context.Context, node *waBinary.Node) {
  42. pairDevice := node.GetChildByTag("pair-device")
  43. err := cli.sendNode(ctx, waBinary.Node{
  44. Tag: "iq",
  45. Attrs: waBinary.Attrs{
  46. "to": node.Attrs["from"],
  47. "id": node.Attrs["id"],
  48. "type": "result",
  49. },
  50. })
  51. if err != nil {
  52. cli.Log.Warnf("Failed to send acknowledgement for pair-device request: %v", err)
  53. }
  54. evt := &events.QR{Codes: make([]string, 0, len(pairDevice.GetChildren()))}
  55. for i, child := range pairDevice.GetChildren() {
  56. if child.Tag != "ref" {
  57. cli.Log.Warnf("pair-device node contains unexpected child tag %s at index %d", child.Tag, i)
  58. continue
  59. }
  60. content, ok := child.Content.([]byte)
  61. if !ok {
  62. cli.Log.Warnf("pair-device node contains unexpected child content type %T at index %d", child, i)
  63. continue
  64. }
  65. evt.Codes = append(evt.Codes, cli.makeQRData(string(content)))
  66. }
  67. cli.dispatchEvent(evt)
  68. }
  69. func (cli *Client) makeQRData(ref string) string {
  70. noise := base64.StdEncoding.EncodeToString(cli.Store.NoiseKey.Pub[:])
  71. identity := base64.StdEncoding.EncodeToString(cli.Store.IdentityKey.Pub[:])
  72. adv := base64.StdEncoding.EncodeToString(cli.Store.AdvSecretKey)
  73. return strings.Join([]string{ref, noise, identity, adv}, ",")
  74. }
  75. func (cli *Client) handlePairSuccess(ctx context.Context, node *waBinary.Node) {
  76. id := node.Attrs["id"].(string)
  77. pairSuccess := node.GetChildByTag("pair-success")
  78. deviceIdentityBytes, _ := pairSuccess.GetChildByTag("device-identity").Content.([]byte)
  79. businessName, _ := pairSuccess.GetChildByTag("biz").Attrs["name"].(string)
  80. jid, _ := pairSuccess.GetChildByTag("device").Attrs["jid"].(types.JID)
  81. lid, _ := pairSuccess.GetChildByTag("device").Attrs["lid"].(types.JID)
  82. platform, _ := pairSuccess.GetChildByTag("platform").Attrs["name"].(string)
  83. go func() {
  84. err := cli.handlePair(ctx, deviceIdentityBytes, id, businessName, platform, jid, lid)
  85. if err != nil {
  86. cli.Log.Errorf("Failed to pair device: %v", err)
  87. cli.Disconnect()
  88. cli.dispatchEvent(&events.PairError{ID: jid, LID: lid, BusinessName: businessName, Platform: platform, Error: err})
  89. } else {
  90. cli.Log.Infof("Successfully paired %s", cli.Store.ID)
  91. cli.dispatchEvent(&events.PairSuccess{ID: jid, LID: lid, BusinessName: businessName, Platform: platform})
  92. }
  93. }()
  94. }
  95. func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, reqID, businessName, platform string, jid, lid types.JID) error {
  96. var deviceIdentityContainer waAdv.ADVSignedDeviceIdentityHMAC
  97. err := proto.Unmarshal(deviceIdentityBytes, &deviceIdentityContainer)
  98. if err != nil {
  99. cli.sendPairError(ctx, reqID, 500, "internal-error")
  100. return &PairProtoError{"failed to parse device identity container in pair success message", err}
  101. }
  102. h := hmac.New(sha256.New, cli.Store.AdvSecretKey)
  103. if deviceIdentityContainer.GetAccountType() == waAdv.ADVEncryptionType_HOSTED {
  104. h.Write(AdvHostedAccountSignaturePrefix)
  105. //cli.Store.IsHosted = true
  106. }
  107. h.Write(deviceIdentityContainer.Details)
  108. if !bytes.Equal(h.Sum(nil), deviceIdentityContainer.HMAC) {
  109. cli.Log.Warnf("Invalid HMAC from pair success message")
  110. cli.sendPairError(ctx, reqID, 401, "hmac-mismatch")
  111. return ErrPairInvalidDeviceIdentityHMAC
  112. }
  113. var deviceIdentity waAdv.ADVSignedDeviceIdentity
  114. err = proto.Unmarshal(deviceIdentityContainer.Details, &deviceIdentity)
  115. if err != nil {
  116. cli.sendPairError(ctx, reqID, 500, "internal-error")
  117. return &PairProtoError{"failed to parse signed device identity in pair success message", err}
  118. }
  119. var deviceIdentityDetails waAdv.ADVDeviceIdentity
  120. err = proto.Unmarshal(deviceIdentity.Details, &deviceIdentityDetails)
  121. if err != nil {
  122. cli.sendPairError(ctx, reqID, 500, "internal-error")
  123. return &PairProtoError{"failed to parse device identity details in pair success message", err}
  124. }
  125. if !verifyAccountSignature(&deviceIdentity, cli.Store.IdentityKey, deviceIdentityDetails.GetDeviceType() == waAdv.ADVEncryptionType_HOSTED) {
  126. cli.sendPairError(ctx, reqID, 401, "signature-mismatch")
  127. return ErrPairInvalidDeviceSignature
  128. }
  129. deviceIdentity.DeviceSignature = generateDeviceSignature(&deviceIdentity, cli.Store.IdentityKey)[:]
  130. if cli.PrePairCallback != nil && !cli.PrePairCallback(jid, platform, businessName) {
  131. cli.sendPairError(ctx, reqID, 500, "internal-error")
  132. return ErrPairRejectedLocally
  133. }
  134. cli.Store.Account = proto.Clone(&deviceIdentity).(*waAdv.ADVSignedDeviceIdentity)
  135. mainDeviceLID := lid
  136. mainDeviceLID.Device = 0
  137. mainDeviceIdentity := *(*[32]byte)(deviceIdentity.AccountSignatureKey)
  138. deviceIdentity.AccountSignatureKey = nil
  139. selfSignedDeviceIdentity, err := proto.Marshal(&deviceIdentity)
  140. if err != nil {
  141. cli.sendPairError(ctx, reqID, 500, "internal-error")
  142. return &PairProtoError{"failed to marshal self-signed device identity", err}
  143. }
  144. cli.Store.ID = &jid
  145. cli.Store.LID = lid
  146. cli.Store.BusinessName = businessName
  147. cli.Store.Platform = platform
  148. err = cli.Store.Save(ctx)
  149. if err != nil {
  150. cli.sendPairError(ctx, reqID, 500, "internal-error")
  151. return &PairDatabaseError{"failed to save device store", err}
  152. }
  153. cli.StoreLIDPNMapping(ctx, lid, jid)
  154. err = cli.Store.Identities.PutIdentity(ctx, mainDeviceLID.SignalAddress().String(), mainDeviceIdentity)
  155. if err != nil {
  156. _ = cli.Store.Delete(ctx)
  157. cli.sendPairError(ctx, reqID, 500, "internal-error")
  158. return &PairDatabaseError{"failed to store main device identity", err}
  159. }
  160. // Expect a disconnect after this and don't dispatch the usual Disconnected event
  161. cli.expectDisconnect()
  162. err = cli.sendNode(ctx, waBinary.Node{
  163. Tag: "iq",
  164. Attrs: waBinary.Attrs{
  165. "to": types.ServerJID,
  166. "type": "result",
  167. "id": reqID,
  168. },
  169. Content: []waBinary.Node{{
  170. Tag: "pair-device-sign",
  171. Content: []waBinary.Node{{
  172. Tag: "device-identity",
  173. Attrs: waBinary.Attrs{
  174. "key-index": deviceIdentityDetails.GetKeyIndex(),
  175. },
  176. Content: selfSignedDeviceIdentity,
  177. }},
  178. }},
  179. })
  180. if err != nil {
  181. _ = cli.Store.Delete(ctx)
  182. return fmt.Errorf("failed to send pairing confirmation: %w", err)
  183. }
  184. return nil
  185. }
  186. func concatBytes(data ...[]byte) []byte {
  187. length := 0
  188. for _, item := range data {
  189. length += len(item)
  190. }
  191. output := make([]byte, length)
  192. ptr := 0
  193. for _, item := range data {
  194. ptr += copy(output[ptr:ptr+len(item)], item)
  195. }
  196. return output
  197. }
  198. func verifyAccountSignature(deviceIdentity *waAdv.ADVSignedDeviceIdentity, ikp *keys.KeyPair, isHosted bool) bool {
  199. if len(deviceIdentity.AccountSignatureKey) != 32 || len(deviceIdentity.AccountSignature) != 64 {
  200. return false
  201. }
  202. signatureKey := ecc.NewDjbECPublicKey(*(*[32]byte)(deviceIdentity.AccountSignatureKey))
  203. signature := *(*[64]byte)(deviceIdentity.AccountSignature)
  204. prefix := AdvAccountSignaturePrefix
  205. if isHosted {
  206. prefix = AdvHostedAccountSignaturePrefix
  207. }
  208. message := concatBytes(prefix, deviceIdentity.Details, ikp.Pub[:])
  209. return ecc.VerifySignature(signatureKey, message, signature)
  210. }
  211. func generateDeviceSignature(deviceIdentity *waAdv.ADVSignedDeviceIdentity, ikp *keys.KeyPair) *[64]byte {
  212. prefix := AdvDeviceSignaturePrefix
  213. message := concatBytes(prefix, deviceIdentity.Details, ikp.Pub[:], deviceIdentity.AccountSignatureKey)
  214. sig := ecc.CalculateSignature(ecc.NewDjbECPrivateKey(*ikp.Priv), message)
  215. return &sig
  216. }
  217. func (cli *Client) sendPairError(ctx context.Context, id string, code int, text string) {
  218. err := cli.sendNode(ctx, waBinary.Node{
  219. Tag: "iq",
  220. Attrs: waBinary.Attrs{
  221. "to": types.ServerJID,
  222. "type": "error",
  223. "id": id,
  224. },
  225. Content: []waBinary.Node{{
  226. Tag: "error",
  227. Attrs: waBinary.Attrs{
  228. "code": code,
  229. "text": text,
  230. },
  231. }},
  232. })
  233. if err != nil {
  234. cli.Log.Errorf("Failed to send pair error node: %v", err)
  235. }
  236. }