diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/pair.go')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/pair.go | 244 |
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, + }, + }}, + } +} |