| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- // 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"
- "context"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "strings"
- "go.mau.fi/libsignal/ecc"
- "google.golang.org/protobuf/proto"
- waBinary "git.bobomao.top/joey/testwh/binary"
- "git.bobomao.top/joey/testwh/proto/waAdv"
- "git.bobomao.top/joey/testwh/types"
- "git.bobomao.top/joey/testwh/types/events"
- "git.bobomao.top/joey/testwh/util/keys"
- )
- var (
- AdvAccountSignaturePrefix = []byte{6, 0}
- AdvDeviceSignaturePrefix = []byte{6, 1}
- AdvHostedAccountSignaturePrefix = []byte{6, 5}
- AdvHostedDeviceSignaturePrefix = []byte{6, 6}
- )
- func (cli *Client) handleIQ(ctx context.Context, 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(ctx, node)
- case "pair-success":
- cli.handlePairSuccess(ctx, node)
- }
- }
- func (cli *Client) handlePairDevice(ctx context.Context, node *waBinary.Node) {
- pairDevice := node.GetChildByTag("pair-device")
- err := cli.sendNode(ctx, 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(ctx context.Context, 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)
- lid, _ := pairSuccess.GetChildByTag("device").Attrs["lid"].(types.JID)
- platform, _ := pairSuccess.GetChildByTag("platform").Attrs["name"].(string)
- go func() {
- err := cli.handlePair(ctx, deviceIdentityBytes, id, businessName, platform, jid, lid)
- if err != nil {
- cli.Log.Errorf("Failed to pair device: %v", err)
- cli.Disconnect()
- cli.dispatchEvent(&events.PairError{ID: jid, LID: lid, BusinessName: businessName, Platform: platform, Error: err})
- } else {
- cli.Log.Infof("Successfully paired %s", cli.Store.ID)
- cli.dispatchEvent(&events.PairSuccess{ID: jid, LID: lid, BusinessName: businessName, Platform: platform})
- }
- }()
- }
- func (cli *Client) handlePair(ctx context.Context, deviceIdentityBytes []byte, reqID, businessName, platform string, jid, lid types.JID) error {
- var deviceIdentityContainer waAdv.ADVSignedDeviceIdentityHMAC
- err := proto.Unmarshal(deviceIdentityBytes, &deviceIdentityContainer)
- if err != nil {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairProtoError{"failed to parse device identity container in pair success message", err}
- }
- h := hmac.New(sha256.New, cli.Store.AdvSecretKey)
- if deviceIdentityContainer.GetAccountType() == waAdv.ADVEncryptionType_HOSTED {
- h.Write(AdvHostedAccountSignaturePrefix)
- //cli.Store.IsHosted = true
- }
- h.Write(deviceIdentityContainer.Details)
- if !bytes.Equal(h.Sum(nil), deviceIdentityContainer.HMAC) {
- cli.Log.Warnf("Invalid HMAC from pair success message")
- cli.sendPairError(ctx, reqID, 401, "hmac-mismatch")
- return ErrPairInvalidDeviceIdentityHMAC
- }
- var deviceIdentity waAdv.ADVSignedDeviceIdentity
- err = proto.Unmarshal(deviceIdentityContainer.Details, &deviceIdentity)
- if err != nil {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairProtoError{"failed to parse signed device identity in pair success message", err}
- }
- var deviceIdentityDetails waAdv.ADVDeviceIdentity
- err = proto.Unmarshal(deviceIdentity.Details, &deviceIdentityDetails)
- if err != nil {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairProtoError{"failed to parse device identity details in pair success message", err}
- }
- if !verifyAccountSignature(&deviceIdentity, cli.Store.IdentityKey, deviceIdentityDetails.GetDeviceType() == waAdv.ADVEncryptionType_HOSTED) {
- cli.sendPairError(ctx, reqID, 401, "signature-mismatch")
- return ErrPairInvalidDeviceSignature
- }
- deviceIdentity.DeviceSignature = generateDeviceSignature(&deviceIdentity, cli.Store.IdentityKey)[:]
- if cli.PrePairCallback != nil && !cli.PrePairCallback(jid, platform, businessName) {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return ErrPairRejectedLocally
- }
- cli.Store.Account = proto.Clone(&deviceIdentity).(*waAdv.ADVSignedDeviceIdentity)
- mainDeviceLID := lid
- mainDeviceLID.Device = 0
- mainDeviceIdentity := *(*[32]byte)(deviceIdentity.AccountSignatureKey)
- deviceIdentity.AccountSignatureKey = nil
- selfSignedDeviceIdentity, err := proto.Marshal(&deviceIdentity)
- if err != nil {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairProtoError{"failed to marshal self-signed device identity", err}
- }
- cli.Store.ID = &jid
- cli.Store.LID = lid
- cli.Store.BusinessName = businessName
- cli.Store.Platform = platform
- err = cli.Store.Save(ctx)
- if err != nil {
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairDatabaseError{"failed to save device store", err}
- }
- cli.StoreLIDPNMapping(ctx, lid, jid)
- err = cli.Store.Identities.PutIdentity(ctx, mainDeviceLID.SignalAddress().String(), mainDeviceIdentity)
- if err != nil {
- _ = cli.Store.Delete(ctx)
- cli.sendPairError(ctx, reqID, 500, "internal-error")
- return &PairDatabaseError{"failed to store main device identity", err}
- }
- // Expect a disconnect after this and don't dispatch the usual Disconnected event
- cli.expectDisconnect()
- err = cli.sendNode(ctx, 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(ctx)
- 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 verifyAccountSignature(deviceIdentity *waAdv.ADVSignedDeviceIdentity, ikp *keys.KeyPair, isHosted bool) bool {
- if len(deviceIdentity.AccountSignatureKey) != 32 || len(deviceIdentity.AccountSignature) != 64 {
- return false
- }
- signatureKey := ecc.NewDjbECPublicKey(*(*[32]byte)(deviceIdentity.AccountSignatureKey))
- signature := *(*[64]byte)(deviceIdentity.AccountSignature)
- prefix := AdvAccountSignaturePrefix
- if isHosted {
- prefix = AdvHostedAccountSignaturePrefix
- }
- message := concatBytes(prefix, deviceIdentity.Details, ikp.Pub[:])
- return ecc.VerifySignature(signatureKey, message, signature)
- }
- func generateDeviceSignature(deviceIdentity *waAdv.ADVSignedDeviceIdentity, ikp *keys.KeyPair) *[64]byte {
- prefix := AdvDeviceSignaturePrefix
- message := concatBytes(prefix, deviceIdentity.Details, ikp.Pub[:], deviceIdentity.AccountSignatureKey)
- sig := ecc.CalculateSignature(ecc.NewDjbECPrivateKey(*ikp.Priv), message)
- return &sig
- }
- func (cli *Client) sendPairError(ctx context.Context, id string, code int, text string) {
- err := cli.sendNode(ctx, 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,
- },
- }},
- })
- if err != nil {
- cli.Log.Errorf("Failed to send pair error node: %v", err)
- }
- }
|