├── gen ├── tools ├── xmpp_decoder │ ├── ui │ │ └── xmpp_ui.go │ └── console │ │ └── xmpp.go ├── fri_analyzer │ ├── config.toml │ └── build_win32 ├── cloner │ ├── build_win32 │ └── cloner.go ├── wam_decoder │ ├── build_win32 │ └── wam_decoder.go ├── upgrade │ ├── TextData_java_object_decode │ │ ├── com │ │ │ └── whatsapp │ │ │ │ ├── TextData.class │ │ │ │ └── TextData.java │ │ └── A.java │ └── upgrade.go ├── jpg_2_jfif │ ├── convert.py │ └── jfif.go └── weight_search │ └── weight_search.go ├── noise ├── README.md ├── hkdf.go ├── LICENSE └── patterns.go ├── def ├── net.go ├── event.go ├── clone │ └── clone.go └── tls.go ├── rpc ├── pb │ ├── gen │ └── Rpc.proto ├── stream.go └── server.go ├── server ├── server.toml ├── build_kali ├── build_win32 └── main.go ├── core ├── core.go ├── test.go ├── version.go ├── log.go ├── timeout.go ├── param.go ├── chatstate.go ├── proxy.go ├── routing_info.go ├── notification.go ├── presence.go ├── jid.go ├── req.go ├── ping.go ├── security.go ├── dirty.go ├── call.go ├── privacy.go ├── session.go ├── safetynet.go ├── vname.go ├── wam_message.go └── multi_device.go ├── signal ├── README.md ├── ecc │ ├── doc.go │ ├── ec_private.go │ ├── ec_public.go │ ├── djb_ec_private.go │ ├── ec_pair.go │ ├── djb_ec_public.go │ ├── curve.go │ └── sign_curve25519.go ├── util │ ├── medium │ │ └── medium.go │ ├── optional │ │ └── int.go │ ├── errorhelper │ │ └── error.go │ ├── bytehelper │ │ └── byte.go │ └── keyhelper │ │ └── key.go ├── fingerprint │ ├── doc.go │ ├── generator.go │ ├── fingerprint.go │ └── display.go ├── state │ ├── store │ │ ├── store.go │ │ ├── message.go │ │ ├── prekey.go │ │ ├── session.go │ │ ├── signed.go │ │ └── identity.go │ └── record │ │ ├── state_pending_prekey.go │ │ ├── prekey.go │ │ ├── unacknowledged_prekey.go │ │ ├── signed_prekey.go │ │ └── state_pending_key_exchange.go ├── groups │ ├── state │ │ ├── store │ │ │ └── sender_key.go │ │ └── record │ │ │ └── sender_key.go │ ├── ratchet │ │ ├── sender_chain_key.go │ │ └── sender_message_key.go │ └── session_builder.go ├── protocol │ ├── ciphertext_message.go │ ├── sender_key_name.go │ ├── signal_protocol_address.go │ ├── sender_key_distribution_message.go │ └── sender_key_message.go ├── pb │ ├── FingerprintProtocol.proto │ ├── SenderMessageKey.proto │ ├── WhisperTextProtocol.proto │ └── LocalStorageProtocol.proto ├── ratchet │ ├── symmetric.go │ ├── receiver.go │ └── sender.go ├── keys │ ├── identity │ │ ├── key_pair.go │ │ └── key.go │ ├── session │ │ ├── derived_secrets.go │ │ └── pair.go │ ├── root │ │ └── key.go │ ├── prekey │ │ └── bundle.go │ ├── message │ │ └── key.go │ └── chain │ │ └── key.go ├── kdf │ └── hkdf.go ├── provision │ └── cipher.go └── cipher │ ├── cipher.go │ └── cbc.go ├── pb ├── VName.proto ├── NoiseHandshake.proto └── NoiseHandshakeDevice.proto ├── dump ├── db ├── def.go ├── log_cfg_production.go ├── log_cfg_debug.go └── log.go ├── go.work ├── xmpp ├── util.go └── node.go ├── go.work.sum ├── README.md ├── wam ├── wam.go ├── chunk.go ├── buffer.go └── record.go ├── pb_gen └── pb_gen.go ├── go.mod ├── net ├── http.go └── Socket.go └── crypto └── crypto.go /gen: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | go run pb_gen/pb_gen.go 3 | -------------------------------------------------------------------------------- /tools/xmpp_decoder/ui/xmpp_ui.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /noise/README.md: -------------------------------------------------------------------------------- 1 | forked from: https://github.com/flynn/noise 2 | -------------------------------------------------------------------------------- /def/net.go: -------------------------------------------------------------------------------- 1 | package def 2 | 3 | var NET_TIMEOUT = 60 // second 4 | -------------------------------------------------------------------------------- /tools/fri_analyzer/config.toml: -------------------------------------------------------------------------------- 1 | LastFile = '/e/namecard.head.fri' 2 | -------------------------------------------------------------------------------- /rpc/pb/gen: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | protoc --go_out=. --go-grpc_out=. *.proto 3 | -------------------------------------------------------------------------------- /server/server.toml: -------------------------------------------------------------------------------- 1 | LogLevel = -1 2 | Pprof = true 3 | Port = 3423 4 | -------------------------------------------------------------------------------- /core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type Core struct{} 4 | 5 | var CORE Core 6 | -------------------------------------------------------------------------------- /signal/README.md: -------------------------------------------------------------------------------- 1 | forked from: https://github.com/RadicalApp/libsignal-protocol-go 2 | -------------------------------------------------------------------------------- /signal/ecc/doc.go: -------------------------------------------------------------------------------- 1 | // Package ecc provides a way to generate, sign, and use Elliptic-Curve 2 | // X25519 Cryptography keys. 3 | package ecc 4 | -------------------------------------------------------------------------------- /signal/util/medium/medium.go: -------------------------------------------------------------------------------- 1 | package medium 2 | 3 | // MaxValue is the maximum possible integer value. 4 | const MaxValue uint32 = 0xFFFFFF 5 | -------------------------------------------------------------------------------- /signal/fingerprint/doc.go: -------------------------------------------------------------------------------- 1 | // Package fingerprint provides a way to generate a visually verifiable 2 | // fingerprint of keys. 3 | package fingerprint 4 | -------------------------------------------------------------------------------- /tools/cloner/build_win32: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | 3 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-s -w" 4 | -------------------------------------------------------------------------------- /tools/wam_decoder/build_win32: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -tags production -ldflags "-s -w" 3 | -------------------------------------------------------------------------------- /tools/upgrade/TextData_java_object_decode/com/whatsapp/TextData.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aj3423/whatsapp-go/master/tools/upgrade/TextData_java_object_decode/com/whatsapp/TextData.class -------------------------------------------------------------------------------- /signal/ecc/ec_private.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | // ECPrivateKeyable is an interface for all elliptic curve private keys. 4 | type ECPrivateKeyable interface { 5 | Serialize() [32]byte 6 | Type() int 7 | } 8 | -------------------------------------------------------------------------------- /core/test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | ) 6 | 7 | func (c Core) Test(j *ajson.Json) *ajson.Json { 8 | a, _ := GetAccFromJson(j) 9 | _ = a 10 | 11 | return NewRet(0) 12 | } 13 | -------------------------------------------------------------------------------- /tools/jpg_2_jfif/convert.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import sys 3 | 4 | jpg_path = "/e/jpg.jpg" 5 | print(sys.argv) 6 | if len(sys.argv) != 3: 7 | sys.exit(1) 8 | img = Image.open(sys.argv[1]) 9 | img.save(sys.argv[2]) 10 | 11 | -------------------------------------------------------------------------------- /core/version.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/def" 6 | ) 7 | 8 | func (c Core) Version(j *ajson.Json) *ajson.Json { 9 | x := ajson.New() 10 | x.Set("biz", def.VERSION_biz) 11 | x.Set("personal", def.VERSION_psnl) 12 | 13 | return NewJsonRet(x) 14 | } 15 | -------------------------------------------------------------------------------- /rpc/pb/Rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = ".;pb"; 4 | 5 | package pb; 6 | 7 | message Json { 8 | string data = 1; // json string 9 | } 10 | 11 | service Rpc { 12 | rpc Exec (Json) returns (Json) {} 13 | rpc Push (Json) returns (stream Json); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tools/fri_analyzer/build_win32: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | 3 | #GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-s -w" 4 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-s -w -extldflags=-static " 5 | -------------------------------------------------------------------------------- /signal/ecc/ec_public.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | // KeySize is the size of EC keys (32) with the EC type byte prepended to it. 4 | const KeySize int = 33 5 | 6 | // ECPublicKeyable is an interface for all elliptic curve public keys. 7 | type ECPublicKeyable interface { 8 | Serialize() []byte 9 | Type() int 10 | PublicKey() [32]byte 11 | } 12 | -------------------------------------------------------------------------------- /signal/state/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import "wa/signal/groups/state/store" 4 | 5 | // SignalProtocol store is an interface that implements the 6 | // methods for all stores needed in the Signal Protocol. 7 | type SignalProtocol interface { 8 | IdentityKey 9 | PreKey 10 | Session 11 | SignedPreKey 12 | store.SenderKey 13 | } 14 | -------------------------------------------------------------------------------- /signal/groups/state/store/sender_key.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "wa/signal/groups/state/record" 5 | "wa/signal/protocol" 6 | ) 7 | 8 | type SenderKey interface { 9 | StoreSenderKey(senderKeyName *protocol.SenderKeyName, keyRecord *record.SenderKey) error 10 | LoadSenderKey(senderKeyName *protocol.SenderKeyName) (*record.SenderKey, error) 11 | } 12 | -------------------------------------------------------------------------------- /pb/VName.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | option go_package = ".;pb"; 4 | 5 | message VName { 6 | message CERT { 7 | optional uint64 Rand_64 = 1; // Math.abs(new SecureRandom().nextLong()); 8 | optional string Smb_wa = 2; // "smb:wa" 9 | optional string Nick = 4; // "" 10 | } 11 | 12 | optional CERT Cert = 1; 13 | optional bytes Signature = 2; 14 | } 15 | -------------------------------------------------------------------------------- /tools/upgrade/TextData_java_object_decode/com/whatsapp/TextData.java: -------------------------------------------------------------------------------- 1 | package com.whatsapp; 2 | 3 | import java.io.*; 4 | 5 | public class TextData implements Serializable{ 6 | public int backgroundColor; 7 | public int fontStyle; 8 | public static final long serialVersionUID = 1L; 9 | public int textColor; 10 | public byte[] thumbnail; 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /dump: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "usage: ./dump AccId"; 5 | echo "eg: ./dump 8613011112222"; 6 | exit 7 | fi 8 | 9 | for i in Profile Device Config Schedule Session Prekey Identity SignedPrekey SenderKey Proxy Message Group GroupMember WamSchedule Cdn; 10 | do 11 | mongodump --db wa --query '{"AccId":'$1'}' --out acc_dump --collection $i 12 | done 13 | -------------------------------------------------------------------------------- /signal/protocol/ciphertext_message.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type CiphertextMessage interface { 4 | Serialize() []byte 5 | Type() uint32 6 | } 7 | 8 | const ( 9 | UnsupportedVersion = 1 10 | CurrentVersion = 3 11 | ) 12 | 13 | const ( 14 | WHISPER_TYPE = 2 15 | PREKEY_TYPE = 3 16 | SENDERKEY_TYPE = 4 17 | SENDERKEY_DISTRIBUTION_TYPE = 5 18 | ) 19 | -------------------------------------------------------------------------------- /db/def.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/mongo" 7 | "go.mongodb.org/mongo-driver/mongo/options" 8 | ) 9 | 10 | var client *mongo.Client 11 | 12 | var ctx = context.Background() 13 | 14 | func init() { 15 | var e error 16 | client, e = mongo.Connect( 17 | ctx, options.Client().ApplyURI("mongodb://127.0.0.1")) 18 | if e != nil { 19 | panic(e) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/log.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | func NewLogHook(a *Acc) func(...any) error { 4 | return func(args ...any) error { 5 | lvl := args[0].(int) 6 | format := args[1].(string) 7 | 8 | // convert []any.Any -> []interface{} 9 | params := []interface{}{} 10 | for _, v := range args[2:] { 11 | var x interface{} 12 | x = v 13 | params = append(params, x) 14 | } 15 | a.Log.DoLog(lvl, format, params...) 16 | 17 | return nil 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/timeout.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/def" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func (c Core) SetTimeout(j *ajson.Json) *ajson.Json { 11 | //a, e := GetAccFromJson(j) 12 | //if e != nil { 13 | //return NewErrRet(e) 14 | //} 15 | v, e := j.Get(`timeout`).TryInt() 16 | if e != nil { 17 | return NewErrRet(errors.Wrap(e, `wrong param "timeout"`)) 18 | } 19 | def.NET_TIMEOUT = v 20 | 21 | return NewRet(0) 22 | } 23 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.18 2 | 3 | use . 4 | 5 | use ../afs 6 | 7 | use ../ahex 8 | 9 | use ../ajson 10 | use ../aregex 11 | use ../acolor 12 | use ../buffer 13 | use ../gui 14 | 15 | use ../algo 16 | 17 | use ../aconfig 18 | 19 | use ../arand 20 | 21 | use ../event 22 | 23 | use ../scope 24 | 25 | use ../phoenix 26 | 27 | use ../i 28 | 29 | use ../str 30 | 31 | use ../L 32 | 33 | use ../console 34 | 35 | use ../android 36 | 37 | use ../run 38 | 39 | use ../aproto 40 | -------------------------------------------------------------------------------- /signal/util/optional/int.go: -------------------------------------------------------------------------------- 1 | package optional 2 | 3 | // NewOptionalUint32 returns an optional Uint32 structure. 4 | func NewOptionalUint32(value uint32) *Uint32 { 5 | return &Uint32{Value: value, IsEmpty: false} 6 | } 7 | 8 | func NewEmptyUint32() *Uint32 { 9 | return &Uint32{IsEmpty: true} 10 | } 11 | 12 | // Uint32 is a simple structure for Uint32 values that can 13 | // optionally be nil. 14 | type Uint32 struct { 15 | Value uint32 16 | IsEmpty bool 17 | } 18 | -------------------------------------------------------------------------------- /signal/fingerprint/generator.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import "wa/signal/keys/identity" 4 | 5 | // FingerprintGenerator is an interface for fingerprint generators. 6 | type FingerprintGenerator interface { 7 | CreateFor(localStableIdentifier, remoteStableIdentifier string, localIdentityKey, remoteIdentityKey *identity.Key) *Fingerprint 8 | CreateForMultiple(localStableIdentifier, remoteStableIdentifier string, localIdentityKey, remoteIdentityKey []*identity.Key) *Fingerprint 9 | } 10 | -------------------------------------------------------------------------------- /signal/pb/FingerprintProtocol.proto: -------------------------------------------------------------------------------- 1 | package pb; 2 | option go_package = ".;pb"; 3 | 4 | option java_package = "org.whispersystems.libsignal.fingerprint"; 5 | option java_outer_classname = "FingerprintProtos"; 6 | 7 | message LogicalFingerprint { 8 | optional bytes content = 1; 9 | // optional bytes identifier = 2; 10 | } 11 | 12 | message CombinedFingerprints { 13 | optional uint32 version = 1; 14 | optional LogicalFingerprint localFingerprint = 2; 15 | optional LogicalFingerprint remoteFingerprint = 3; 16 | } -------------------------------------------------------------------------------- /xmpp/util.go: -------------------------------------------------------------------------------- 1 | package xmpp 2 | 3 | import "fmt" 4 | 5 | const NotJid = 0 6 | const NormalJid = 1 7 | const DevJid = 2 8 | 9 | func analyze_jid(jid string) (user int, agent, device uint8, server string, jid_type int) { 10 | _, e := fmt.Sscanf(jid, 11 | "%d.%d:%d@%s", &user, &agent, &device, &server) 12 | 13 | if e == nil { 14 | jid_type = DevJid 15 | return 16 | } 17 | 18 | _, e = fmt.Sscanf(jid, 19 | "%d@%s", &user, &server) 20 | if e == nil { 21 | jid_type = NormalJid 22 | return 23 | } 24 | return 25 | } 26 | 27 | -------------------------------------------------------------------------------- /pb/NoiseHandshake.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | option go_package = ".;pb"; 4 | 5 | message PatternStep { 6 | optional bytes S1 = 1; 7 | optional bytes S2 = 2; 8 | optional bytes S3 = 3; 9 | } 10 | 11 | message PatternXX { 12 | optional PatternStep E = 2; // e 13 | optional PatternStep E_EE_S_ES = 3; // e, ee, s, es 14 | optional PatternStep S_SE = 4; // s, se 15 | } 16 | 17 | message PatternIK { 18 | optional PatternStep E_EE_S_ES = 2; // e, es, s, ss -> 19 | optional PatternStep E_EE_SE = 3; // <- e, ee, se 20 | } 21 | -------------------------------------------------------------------------------- /signal/ratchet/symmetric.go: -------------------------------------------------------------------------------- 1 | package ratchet 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/keys/identity" 6 | ) 7 | 8 | // SymmetricParameters describes the session parameters for sessions where 9 | // both users are online, which doesn't use prekeys for setup. 10 | type SymmetricParameters struct { 11 | OurBaseKey *ecc.ECKeyPair 12 | OurRatchetKey *ecc.ECKeyPair 13 | OurIdentityKeyPair *identity.KeyPair 14 | 15 | TheirBaseKey ecc.ECPublicKeyable 16 | TheirRatchetKey ecc.ECPublicKeyable 17 | TheirIdentityKey *identity.Key 18 | } 19 | -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 2 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 3 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 4 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 5 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 6 | golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 7 | -------------------------------------------------------------------------------- /db/log_cfg_production.go: -------------------------------------------------------------------------------- 1 | //go:build production 2 | // +build production 3 | 4 | package db 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "L" 11 | "go.mongodb.org/mongo-driver/bson" 12 | ) 13 | 14 | func init() { 15 | L.EnableColor(false) 16 | } 17 | 18 | func (l *Logger) DoLog(level int, format string, args ...interface{}) { 19 | if level >= LogLevel { 20 | bs := bson.M{ 21 | `Time`: time.Now(), // for TTL 22 | `AccId`: l.AccId, 23 | `Level`: level, 24 | `Text`: fmt.Sprintf(format, args...), 25 | } 26 | colLog.InsertOne(ctx, bs) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /signal/state/store/message.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | // MessageKey store is an interface describing the optional local storage 4 | // of message keys. 5 | //type MessageKey interface { 6 | //// Load a local message key by id 7 | //LoadMessageKey(keyID uint32) *message.Keys 8 | 9 | //// Store a local message key 10 | //StoreMessageKey(keyID uint32, key *message.Keys) 11 | 12 | //// Check to see if the store contains a message key with id. 13 | //ContainsMessageKey(keyID uint32) bool 14 | 15 | //// Delete a message key from local storage. 16 | //RemoveMessageKey(keyID uint32) 17 | //} 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a Golang implementation of WhatsApp, reverse engineered from Android version, for learning purpose only. 2 | 3 | features 4 | ==== 5 | It contains almost all the features of WA and WA Business, include and not limited to: 6 | - register (customize device, proxy, dns, tls fingerprint) 7 | - login (or clone session from phone files) 8 | - contact (upload, syncing, query) 9 | - message (personal/group, type: text, image, video, ptt, contact, url, document) 10 | - others like: multi device, 2FA, chat state, heartbeat, presence, cdn, notifications... 11 | 12 | Last updated 13 | ==== 14 | 2022.11 15 | -------------------------------------------------------------------------------- /signal/state/store/prekey.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import "wa/signal/state/record" 4 | 5 | // PreKey store is an interface describing the local storage 6 | // of PreKeyRecords 7 | type PreKey interface { 8 | // Load a local PreKeyRecord 9 | LoadPreKey(preKeyID uint32) (*record.PreKey, error) 10 | 11 | // Store a local PreKeyRecord 12 | StorePreKey(preKeyID uint32, preKeyRecord *record.PreKey) error 13 | 14 | // Check to see if the store contains a PreKeyRecord 15 | ContainsPreKey(preKeyID uint32) bool 16 | 17 | // Delete a PreKeyRecord from local storage. 18 | RemovePreKey(preKeyID uint32) 19 | } 20 | -------------------------------------------------------------------------------- /signal/state/store/session.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "wa/signal/protocol" 5 | "wa/signal/state/record" 6 | ) 7 | 8 | // Session store is an interface for the persistent storage of session 9 | // state information for remote clients. 10 | type Session interface { 11 | LoadSession(address *protocol.SignalAddress) (*record.Session, error) 12 | GetSubDeviceSessions(name string) []uint32 13 | StoreSession(remoteAddress *protocol.SignalAddress, record *record.Session) error 14 | ContainsSession(remoteAddress *protocol.SignalAddress) bool 15 | DeleteSession(remoteAddress *protocol.SignalAddress) 16 | DeleteAllSessions() 17 | } 18 | -------------------------------------------------------------------------------- /core/param.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func NewRet(ec int) *ajson.Json { 10 | j := ajson.New() 11 | j.Set("ErrCode", ec) 12 | return j 13 | } 14 | func NewSucc() *ajson.Json { 15 | return NewRet(0) 16 | } 17 | func NewErrRet(e error) *ajson.Json { 18 | j := ajson.New() 19 | j.Set("ErrCode", 3423) 20 | j.Set("ErrMsg", e.Error()) 21 | return j 22 | } 23 | func NewCrashRet() *ajson.Json { 24 | return NewErrRet(errors.New("Crashed...")) 25 | } 26 | 27 | func NewJsonRet(result *ajson.Json) *ajson.Json { 28 | j := NewSucc() 29 | j.Set(`Result`, result) 30 | return j 31 | } 32 | -------------------------------------------------------------------------------- /server/build_kali: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | if test -e ./server 3 | rm server 4 | end 5 | if test -e ./server.linux.zip 6 | rm server.linux.zip 7 | end 8 | if test -e ./test.go 9 | rm test.go 10 | end 11 | if test -e ./Rpc.proto 12 | rm Rpc.proto 13 | end 14 | 15 | set bt (date '+%Y-%m-%d %H:%M:%S') 16 | CGO_LDFLAGS="-static" go build -buildvcs=false -tags production -ldflags "-s -w -X 'main.BuildTime=$bt'" 17 | 18 | if test -e ./server 19 | echo "---- build success ----" 20 | 21 | # cp ../test/test.go . 22 | # cp ../rpc/pb/Rpc.proto . 23 | # zip server.linux.zip server test.go Rpc.proto 24 | # 25 | # #rm server 26 | # rm test.go 27 | # rm Rpc.proto 28 | end 29 | -------------------------------------------------------------------------------- /wam/wam.go: -------------------------------------------------------------------------------- 1 | package wam 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | ) 7 | 8 | var Head = []byte{ 9 | 0x57, 0x41, 0x4d, 0x05, 10 | } 11 | 12 | func Parse( 13 | bs []byte, 14 | ) ([]*Record, error) { 15 | if len(bs) < len(Head) || !bytes.Equal(bs[0:len(Head)], Head) { 16 | return nil, errors.New(`less8`) 17 | } 18 | offset := 8 // skip header 19 | 20 | ret := []*Record{} 21 | rest := len(bs) - offset 22 | 23 | for rest > 0 { 24 | rec := &Record{} 25 | used_len, e := rec.Parse(bs[offset:]) 26 | if e != nil { 27 | return nil, e 28 | } 29 | ret = append(ret, rec) 30 | 31 | rest -= used_len 32 | offset += used_len 33 | } 34 | 35 | return ret, nil 36 | } 37 | -------------------------------------------------------------------------------- /signal/keys/identity/key_pair.go: -------------------------------------------------------------------------------- 1 | package identity 2 | 3 | import "wa/signal/ecc" 4 | 5 | func NewKeyPair(publicKey *Key, privateKey ecc.ECPrivateKeyable) *KeyPair { 6 | keyPair := KeyPair{ 7 | publicKey: publicKey, 8 | privateKey: privateKey, 9 | } 10 | 11 | return &keyPair 12 | } 13 | 14 | //func NewKeyPairFromBytes(priv, pub []byte) *KeyPair { 15 | //} 16 | 17 | type KeyPair struct { 18 | publicKey *Key 19 | privateKey ecc.ECPrivateKeyable 20 | } 21 | 22 | func (k *KeyPair) PublicKey() *Key { 23 | return k.publicKey 24 | } 25 | 26 | func (k *KeyPair) PrivateKey() ecc.ECPrivateKeyable { 27 | return k.privateKey 28 | } 29 | 30 | //func (k *KeyPair) Serialize() []byte { 31 | //} 32 | -------------------------------------------------------------------------------- /server/build_win32: -------------------------------------------------------------------------------- 1 | #!/bin/fish 2 | if test -e ./server.exe 3 | rm server.exe 4 | end 5 | if test -e ./server.exe.zip 6 | rm server.exe.zip 7 | end 8 | if test -e ./test.go 9 | rm test.go 10 | end 11 | if test -e ./Rpc.proto 12 | rm Rpc.proto 13 | end 14 | 15 | set bt (date '+%Y-%m-%d %H:%M:%S') 16 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -tags production -ldflags "-s -w -X 'main.BuildTime=$bt'" 17 | if test -e ./server.exe 18 | echo "build success" 19 | 20 | cp ../test/test.go . 21 | cp ../rpc/pb/Rpc.proto . 22 | zip server.exe.zip server.exe test.go Rpc.proto 23 | 24 | rm server.exe 25 | rm test.go 26 | rm Rpc.proto 27 | end 28 | 29 | -------------------------------------------------------------------------------- /def/event.go: -------------------------------------------------------------------------------- 1 | package def 2 | 3 | const ( 4 | // my 5 | Ev_AccCreate = "acc_create" 6 | Ev_AccOn = "acc_on" 7 | Ev_AccOff = "acc_off" 8 | Ev_Connected = "connected" 9 | Ev_NoiseDisconnected = "disconnected" 10 | Ev_Log = "log" 11 | Ev_Noise_Location = "noise_location" 12 | Ev_Heartbeat = "heartbeat" 13 | Ev_Retry = "retry" 14 | Ev_Success = "success" 15 | Ev_Push = "push" 16 | 17 | // dont modify string, it's Tag of xmpp 18 | Ev_call = "call" 19 | Ev_iq = "iq" 20 | Ev_message = "message" 21 | Ev_receipt = "receipt" 22 | Ev_ib = "ib" 23 | Ev_notification = "notification" 24 | ) 25 | -------------------------------------------------------------------------------- /signal/protocol/sender_key_name.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // NewSenderKeyName returns a new SenderKeyName object. 4 | func NewSenderKeyName(groupID string, sender *SignalAddress) *SenderKeyName { 5 | return &SenderKeyName{ 6 | groupID: groupID, 7 | sender: sender, 8 | } 9 | } 10 | 11 | // SenderKeyName is a structure for a group session address. 12 | type SenderKeyName struct { 13 | groupID string 14 | sender *SignalAddress 15 | } 16 | 17 | // GroupID returns the sender key group id 18 | func (n *SenderKeyName) GroupID() string { 19 | return n.groupID 20 | } 21 | 22 | // Sender returns the Signal address of sending user in the group. 23 | func (n *SenderKeyName) Sender() *SignalAddress { 24 | return n.sender 25 | } 26 | -------------------------------------------------------------------------------- /core/chatstate.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/xmpp" 6 | ) 7 | 8 | func (c Core) ChatState(j *ajson.Json) *ajson.Json { 9 | a, e := GetAccFromJson(j) 10 | if e != nil { 11 | return NewErrRet(e) 12 | } 13 | 14 | jid := j.Get(`jid`).String() 15 | 16 | ch := &xmpp.Node{ 17 | Tag: j.Get(`state`).String(), 18 | } 19 | if j.Get(`media_type`).String() == `ptt` { 20 | ch.SetAttr(`media`, `audio`) 21 | } 22 | 23 | e = a.Noise.WriteXmppNode(&xmpp.Node{ 24 | Tag: `chatstate`, 25 | Attrs: []*xmpp.KeyValue{ 26 | {Key: `to`, Value: jid, Type: 1}, 27 | }, 28 | Children: []*xmpp.Node{ 29 | ch, 30 | }, 31 | }) 32 | if e != nil { 33 | return NewErrRet(e) 34 | } 35 | 36 | return NewSucc() 37 | } 38 | -------------------------------------------------------------------------------- /tools/weight_search/weight_search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | "github.com/fatih/color" 9 | "run" 10 | ) 11 | 12 | func main() { 13 | 14 | if len(os.Args) != 2 { 15 | color.HiRed("usage:") 16 | fmt.Println(" exe 1234") 17 | } 18 | num_str := os.Args[1] 19 | num, e := strconv.Atoi(num_str) 20 | if e != nil { 21 | panic(e) 22 | } 23 | 24 | { 25 | 26 | color.HiMagenta("decimal:") 27 | stdout, _, _ := run.RunCommand("/root/Downloads/src", "ag", "--color", fmt.Sprintf("\\(%d,", num)) 28 | fmt.Println(stdout) 29 | } 30 | { 31 | color.HiMagenta("hex:") 32 | stdout, _, _ := run.RunCommand("/root/Downloads/src", "ag", "--color", fmt.Sprintf("\\(0x%x,", num)) 33 | fmt.Println(stdout) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tools/upgrade/TextData_java_object_decode/A.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import com.whatsapp.*; 3 | 4 | public class A { 5 | public static void main(String[] args) { 6 | String name; 7 | String filename = "java.bin"; 8 | 9 | try 10 | { 11 | FileInputStream file = new FileInputStream(filename); 12 | ObjectInputStream out = new ObjectInputStream(file); 13 | 14 | 15 | TextData t = (TextData)out.readObject(); 16 | 17 | System.out.println(t.backgroundColor); 18 | System.out.println(t.fontStyle); 19 | System.out.println(t.textColor); 20 | System.out.println(t.thumbnail); 21 | 22 | 23 | out.close(); 24 | file.close(); 25 | } 26 | catch(Exception e) 27 | { 28 | System.out.println("Exception: " + e.toString()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /signal/state/store/signed.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import "wa/signal/state/record" 4 | 5 | // SignedPreKey store is an interface that describes how to persistently 6 | // store signed PreKeys. 7 | type SignedPreKey interface { 8 | // LoadSignedPreKey loads a local SignedPreKeyRecord 9 | LoadSignedPreKey(signedPreKeyID uint32) (*record.SignedPreKey, error) 10 | 11 | // LoadSignedPreKeys loads all local SignedPreKeyRecords 12 | LoadSignedPreKeys() []*record.SignedPreKey 13 | 14 | // Store a local SignedPreKeyRecord 15 | StoreSignedPreKey(signedPreKeyID uint32, record *record.SignedPreKey) error 16 | 17 | // Check to see if store contains the given record 18 | ContainsSignedPreKey(signedPreKeyID uint32) bool 19 | 20 | // Delete a SignedPreKeyRecord from local storage 21 | RemoveSignedPreKey(signedPreKeyID uint32) 22 | } 23 | -------------------------------------------------------------------------------- /signal/ecc/djb_ec_private.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | // NewDjbECPrivateKey returns a new EC private key with the given bytes. 4 | func NewDjbECPrivateKey(key [32]byte) *DjbECPrivateKey { 5 | private := DjbECPrivateKey{ 6 | privateKey: key, 7 | } 8 | return &private 9 | } 10 | 11 | // DjbECPrivateKey implements the ECPrivateKey interface and uses Curve25519. 12 | type DjbECPrivateKey struct { 13 | privateKey [32]byte 14 | } 15 | 16 | // PrivateKey returns the private key as a byte-array. 17 | func (d *DjbECPrivateKey) PrivateKey() [32]byte { 18 | return d.privateKey 19 | } 20 | 21 | // Serialize returns the private key as a byte-array. 22 | func (d *DjbECPrivateKey) Serialize() [32]byte { 23 | return d.privateKey 24 | } 25 | 26 | // Type returns the EC type value. 27 | func (d *DjbECPrivateKey) Type() int { 28 | return DjbType 29 | } 30 | -------------------------------------------------------------------------------- /signal/keys/session/derived_secrets.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | // NewDerivedSecrets returns a new RootKey/ChainKey pair from 64 bytes of key material 4 | // generated by the key derivation function. 5 | func NewDerivedSecrets(keyMaterial []byte) *DerivedSecrets { 6 | secrets := DerivedSecrets{ 7 | keyMaterial[:32], 8 | keyMaterial[32:], 9 | } 10 | 11 | return &secrets 12 | } 13 | 14 | // DerivedSecrets is a structure for holding the derived secrets for the 15 | // Root and Chain keys for a session. 16 | type DerivedSecrets struct { 17 | rootKey []byte 18 | chainKey []byte 19 | } 20 | 21 | // RootKey returns the RootKey bytes. 22 | func (d *DerivedSecrets) RootKey() []byte { 23 | return d.rootKey 24 | } 25 | 26 | // ChainKey returns the ChainKey bytes. 27 | func (d *DerivedSecrets) ChainKey() []byte { 28 | return d.chainKey 29 | } 30 | -------------------------------------------------------------------------------- /signal/ecc/ec_pair.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | // NewECKeyPair returns a new elliptic curve keypair given the specified public and private keys. 4 | func NewECKeyPair(publicKey ECPublicKeyable, privateKey ECPrivateKeyable) *ECKeyPair { 5 | keypair := ECKeyPair{ 6 | publicKey: publicKey, 7 | privateKey: privateKey, 8 | } 9 | 10 | return &keypair 11 | } 12 | 13 | // ECKeyPair is a combination of both public and private elliptic curve keys. 14 | type ECKeyPair struct { 15 | publicKey ECPublicKeyable 16 | privateKey ECPrivateKeyable 17 | } 18 | 19 | // PublicKey returns the public key from the key pair. 20 | func (e *ECKeyPair) PublicKey() ECPublicKeyable { 21 | return e.publicKey 22 | } 23 | 24 | // PrivateKey returns the private key from the key pair. 25 | func (e *ECKeyPair) PrivateKey() ECPrivateKeyable { 26 | return e.privateKey 27 | } 28 | -------------------------------------------------------------------------------- /core/proxy.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "ajson" 7 | ) 8 | 9 | func (c Core) SetProxy(j *ajson.Json) *ajson.Json { 10 | a, e := GetAccFromJson(j) 11 | if e != nil { 12 | return NewErrRet(e) 13 | } 14 | 15 | if addr, e := j.Get(`Addr`).TryString(); e == nil { 16 | if e := a.Store.SetProxy(addr); e != nil { 17 | return NewErrRet(e) 18 | } 19 | a.Noise.Socket.Proxy = addr 20 | a.Log.Info("SetProxy: " + addr) 21 | } 22 | 23 | if dns, e := j.Get(`Dns`).TryMap(); e == nil { 24 | // copy to map 25 | m := map[string]string{} 26 | for k, v := range dns { 27 | m[k] = v.(string) 28 | } 29 | if e := a.Store.SetDns(m); e != nil { 30 | return NewErrRet(e) 31 | } 32 | a.Noise.Socket.Dns = m 33 | { // log 34 | bs, _ := json.Marshal(m) 35 | a.Log.Info("SetDns: " + string(bs)) 36 | } 37 | } 38 | 39 | return NewRet(0) 40 | } 41 | -------------------------------------------------------------------------------- /rpc/stream.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "sync" 5 | 6 | "wa/rpc/pb" 7 | ) 8 | 9 | type Stream struct { 10 | stream pb.Rpc_PushServer 11 | chClose chan struct{} 12 | 13 | mu sync.RWMutex 14 | isAlive bool 15 | } 16 | 17 | func NewStream(con pb.Rpc_PushServer) *Stream { 18 | return &Stream{ 19 | stream: con, 20 | chClose: make(chan struct{}), 21 | } 22 | } 23 | func (st *Stream) Close() { 24 | st.mu.RLock() 25 | defer st.mu.RUnlock() 26 | 27 | if st.isAlive { 28 | st.chClose <- struct{}{} 29 | } 30 | } 31 | func (st *Stream) SetAlive(alive bool) { 32 | st.mu.Lock() 33 | st.isAlive = alive 34 | st.mu.Unlock() 35 | } 36 | 37 | func (st *Stream) KeepAlive() { 38 | st.SetAlive(true) 39 | 40 | defer st.SetAlive(false) 41 | 42 | ctx := st.stream.Context() 43 | select { 44 | case <-st.chClose: 45 | break 46 | case <-ctx.Done(): 47 | break 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /signal/fingerprint/fingerprint.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | // NewFingerprint will return a new Fingerprint structure. 4 | func NewFingerprint(displayFingerprint *Display) *Fingerprint { 5 | return &Fingerprint{ 6 | fingerprintDisplay: displayFingerprint, 7 | } 8 | } 9 | 10 | // Fingerprint is a structure for returning a displayable and scannable 11 | // fingerprint for identity verification. 12 | type Fingerprint struct { 13 | fingerprintDisplay *Display 14 | fingerprintScan string 15 | } 16 | 17 | // Display will return a fingerprint display structure for getting a 18 | // string representation of given keys. 19 | func (f *Fingerprint) Display() *Display { 20 | return f.fingerprintDisplay 21 | } 22 | 23 | // Scan will return a fingerprint scan structure for getting a scannable 24 | // representation of given keys. 25 | func (f *Fingerprint) Scan() string { 26 | return f.fingerprintScan 27 | } 28 | -------------------------------------------------------------------------------- /core/routing_info.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "wa/xmpp" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | ) 8 | 9 | /* 10 | { 11 | "Attrs": { 12 | "from": "s.whatsapp.net" 13 | }, 14 | "Children": [ 15 | { 16 | "Children": [ 17 | { 18 | "Data": "080c0805", 19 | "Tag": "routing_info" 20 | } 21 | ], 22 | "Tag": "edge_routing" 23 | } 24 | ], 25 | "Tag": "ib" 26 | } 27 | */ 28 | 29 | func New_Hook_RoutingInfo(a *Acc) func(...any) error { 30 | return func(args ...any) error { 31 | n := args[0].(*xmpp.Node) 32 | 33 | // if contains child `edge_routing` 34 | ch, ok := n.FindChildByTag(`edge_routing`) 35 | if !ok { 36 | return nil 37 | } 38 | ch2, ok := ch.FindChildByTag(`routing_info`) 39 | if !ok { 40 | return nil 41 | } 42 | 43 | return a.Store.ModifyConfig(bson.M{ 44 | `RoutingInfo`: ch2.Data, 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /def/clone/clone.go: -------------------------------------------------------------------------------- 1 | package clone 2 | 3 | // for clone Phone acc -> bot 4 | 5 | type Gene struct { 6 | Nick string 7 | Fdid string 8 | RoutingInfo []byte 9 | NoiseLocation string 10 | AbPropsConfigKey string 11 | AbPropsConfigHash string 12 | ServerPropsConfigKey string 13 | ServerPropsConfigHash string 14 | 15 | Prekeys []Prekey 16 | Identity 17 | SignedPrekey 18 | 19 | StaticPub []byte 20 | StaticPriv []byte 21 | //RemoteStatic []byte 22 | } 23 | 24 | type Prekey struct { 25 | PrekeyId int 26 | SentToServer bool 27 | Record []byte 28 | DirectDistribution bool 29 | } 30 | type Identity struct { 31 | RecipientId int 32 | DeviceId int 33 | RegistrationId int 34 | PublicKey []byte 35 | PrivateKey []byte 36 | NextPrekeyId int 37 | } 38 | type SignedPrekey struct { 39 | PrekeyId int 40 | Record []byte 41 | } 42 | -------------------------------------------------------------------------------- /core/notification.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "wa/xmpp" 5 | ) 6 | 7 | func New_Hook_Notification(a *Acc) func(...any) error { 8 | // Decrypt and Replace node.Data 9 | return func(args ...any) error { 10 | n := args[0].(*xmpp.Node) 11 | 12 | type_, _ := n.GetAttr(`type`) 13 | from, _ := n.GetAttr(`from`) 14 | id, _ := n.GetAttr(`id`) 15 | participant, _ := n.GetAttr(`participant`) 16 | 17 | if type_ == `` || from == `` || id == `` { 18 | return nil 19 | } 20 | attrs := []*xmpp.KeyValue{ 21 | {Key: `class`, Value: `notification`}, 22 | {Key: `id`, Value: id}, 23 | {Key: `to`, Value: from}, 24 | {Key: `type`, Value: type_}, 25 | } 26 | if len(participant) > 0 { 27 | attrs = append(attrs, &xmpp.KeyValue{ 28 | Key: `participant`, Value: participant, 29 | }) 30 | } 31 | a.Noise.WriteXmppNode(&xmpp.Node{ 32 | Tag: `ack`, 33 | Attrs: attrs, 34 | }) 35 | 36 | return nil 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /signal/util/errorhelper/error.go: -------------------------------------------------------------------------------- 1 | package errorhelper 2 | 3 | // NewMultiError returns a new MultiError object. 4 | func NewMultiError() *MultiError { 5 | return &MultiError{ 6 | errors: []error{}, 7 | } 8 | } 9 | 10 | // MultiError is a structure for holding multiple errors so they 11 | // can be checked at a later point. 12 | type MultiError struct { 13 | errors []error 14 | } 15 | 16 | // Add will add the given error if it is not nil. 17 | func (m *MultiError) Add(err error) { 18 | if err != nil { 19 | m.errors = append(m.errors, err) 20 | } 21 | } 22 | 23 | // HasErrors will return true if any non-nil errors have been 24 | // added. 25 | func (m *MultiError) HasErrors() bool { 26 | if len(m.errors) > 0 { 27 | return true 28 | } 29 | 30 | return false 31 | } 32 | 33 | // Error will print the first error is encountered. 34 | func (m *MultiError) Error() string { 35 | if !m.HasErrors() { 36 | return "" 37 | } 38 | 39 | return m.errors[0].Error() 40 | } 41 | -------------------------------------------------------------------------------- /signal/protocol/signal_protocol_address.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ADDRESS_SEPARATOR = ":" 8 | 9 | // NewSignalAddress returns a new signal address. 10 | func NewSignalAddress(name string, deviceID uint32) *SignalAddress { 11 | addr := SignalAddress{ 12 | name: name, 13 | deviceID: deviceID, 14 | } 15 | 16 | return &addr 17 | } 18 | 19 | // SignalAddress is a combination of a name and a device ID. 20 | type SignalAddress struct { 21 | name string 22 | deviceID uint32 23 | } 24 | 25 | // Name returns the signal address's name. 26 | func (s *SignalAddress) Name() string { 27 | return s.name 28 | } 29 | 30 | // DeviceID returns the signal address's device ID. 31 | func (s *SignalAddress) DeviceID() uint32 { 32 | return s.deviceID 33 | } 34 | 35 | // String returns a string of both the address name and device id. 36 | func (s *SignalAddress) String() string { 37 | return s.name + ADDRESS_SEPARATOR + fmt.Sprint(s.deviceID) 38 | } 39 | -------------------------------------------------------------------------------- /core/presence.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/xmpp" 6 | ) 7 | 8 | func (a *Acc) presence(type_, nick, jid string) error { 9 | // type == `available`/`unavailable`/`subscribe` 10 | Attrs := []*xmpp.KeyValue{ 11 | {Key: `type`, Value: type_}, 12 | } 13 | 14 | if type_ == `subscribe` { 15 | Attrs = append(Attrs, &xmpp.KeyValue{ 16 | Key: `to`, Value: jid, 17 | }) 18 | } 19 | // has nick 20 | if len(nick) > 0 { 21 | Attrs = append(Attrs, &xmpp.KeyValue{ 22 | Key: `name`, Value: nick, 23 | }) 24 | } 25 | 26 | return a.Noise.WriteXmppNode(&xmpp.Node{ 27 | Tag: `presence`, 28 | Attrs: Attrs, 29 | }) 30 | } 31 | 32 | func (c Core) Presence(j *ajson.Json) *ajson.Json { 33 | a, e := GetAccFromJson(j) 34 | if e != nil { 35 | return NewErrRet(e) 36 | } 37 | type_ := j.Get(`type`).String() 38 | jid := j.Get(`jid`).String() 39 | nick := j.Get(`nick`).String() 40 | e = a.presence(type_, nick, jid) 41 | if e != nil { 42 | return NewErrRet(e) 43 | } 44 | 45 | return NewSucc() 46 | } 47 | -------------------------------------------------------------------------------- /core/jid.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // 8613011112222.0:1@s.whatsapp.net -> 9613011112222@s.whatsapp.net 11 | func clear_jid_device(jid string) string { 12 | p := strings.Index(jid, ":") 13 | if p > 0 { 14 | return jid[0:p-2] + jid[p+2:] 15 | } 16 | return jid 17 | } 18 | 19 | // 8613322222222.0:1@s.whatsapp.net -> 8613322222222 1 20 | func split_jid(jid string) (recid uint64, devid uint32, e error) { 21 | jid = strings.ReplaceAll(jid, "@s.whatsapp.net", "") 22 | 23 | { // recid 24 | v := strings.Split(jid, ".") 25 | if len(v) == 0 { 26 | e = errors.New("invalid jid: " + jid) 27 | return 28 | } 29 | 30 | var x int 31 | x, e = strconv.Atoi(v[0]) 32 | if e != nil { 33 | return 34 | } 35 | recid = uint64(x) 36 | } 37 | { // devid 38 | v := strings.Split(jid, ":") 39 | if len(v) == 2 { 40 | var x int 41 | x, e = strconv.Atoi(v[1]) 42 | if e != nil { 43 | return 44 | } 45 | devid = uint32(x) 46 | } 47 | } 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /core/req.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | /* 4 | func (c Core) _Req(j *ajson.Json, wait_for_resp bool) *ajson.Json { 5 | a, e := GetAccFromJson(j) 6 | if e != nil { 7 | return NewErrRet(e) 8 | } 9 | 10 | n, e := xmpp.ParseJson(j) 11 | if e != nil { 12 | return NewErrRet(errors.Wrap(e, `fail parse json`)) 13 | } 14 | 15 | if wait_for_resp { 16 | // 1. 17 | // if wait_for_resp but no `id` provided 18 | // auto generate one 19 | id, ok := n.GetAttr(`id`) 20 | if !ok || id == `auto` || id == `` { 21 | n.SetAttr(`id`, a.Noise.NextIqId_2()) 22 | } 23 | 24 | // 2. send 25 | nr, e := a.Noise.WriteReadXmppNode(n) 26 | if e != nil { 27 | return NewErrRet(e) 28 | } 29 | return NewJsonRet(nr.ToJson()) 30 | } else { 31 | e := a.Noise.WriteXmppNode(n) 32 | if e != nil { 33 | return NewErrRet(e) 34 | } 35 | return NewSucc() 36 | } 37 | 38 | } 39 | func (c Core) Req(j *ajson.Json) *ajson.Json { 40 | return c._Req(j, false) 41 | } 42 | func (c Core) ReqResp(j *ajson.Json) *ajson.Json { 43 | return c._Req(j, true) 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /noise/hkdf.go: -------------------------------------------------------------------------------- 1 | package noise 2 | 3 | import ( 4 | "crypto/hmac" 5 | "hash" 6 | ) 7 | 8 | func hkdf(h func() hash.Hash, outputs int, out1, out2, out3, chainingKey, inputKeyMaterial []byte) ([]byte, []byte, []byte) { 9 | if len(out1) > 0 { 10 | panic("len(out1) > 0") 11 | } 12 | if len(out2) > 0 { 13 | panic("len(out2) > 0") 14 | } 15 | if len(out3) > 0 { 16 | panic("len(out3) > 0") 17 | } 18 | if outputs > 3 { 19 | panic("outputs > 3") 20 | } 21 | 22 | tempMAC := hmac.New(h, chainingKey) 23 | tempMAC.Write(inputKeyMaterial) 24 | tempKey := tempMAC.Sum(out2) 25 | 26 | out1MAC := hmac.New(h, tempKey) 27 | out1MAC.Write([]byte{0x01}) 28 | out1 = out1MAC.Sum(out1) 29 | 30 | if outputs == 1 { 31 | return out1, nil, nil 32 | } 33 | 34 | out2MAC := hmac.New(h, tempKey) 35 | out2MAC.Write(out1) 36 | out2MAC.Write([]byte{0x02}) 37 | out2 = out2MAC.Sum(out2) 38 | 39 | if outputs == 2 { 40 | return out1, out2, nil 41 | } 42 | 43 | out3MAC := hmac.New(h, tempKey) 44 | out3MAC.Write(out2) 45 | out3MAC.Write([]byte{0x03}) 46 | out3 = out3MAC.Sum(out3) 47 | 48 | return out1, out2, out3 49 | } 50 | -------------------------------------------------------------------------------- /core/ping.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "event" 5 | "wa/xmpp" 6 | ) 7 | 8 | func send_ping_ack(a *Acc) { 9 | a.Noise.WriteXmppNode(&xmpp.Node{ 10 | Tag: `iq`, 11 | Attrs: []*xmpp.KeyValue{ 12 | {Key: `to`, Value: `s.whatsapp.net`, Type: 1}, 13 | {Key: `type`, Value: `result`}, 14 | }, 15 | }) 16 | } 17 | func New_Hook_ServerPing(a *Acc) func(...any) error { 18 | return func(args ...any) error { 19 | n := args[0].(*xmpp.Node) 20 | 21 | xmlns, _ := n.GetAttr(`xmlns`) 22 | 23 | if xmlns == "urn:xmpp:ping" { 24 | send_ping_ack(a) 25 | 26 | return event.Stop 27 | } 28 | 29 | return nil 30 | } 31 | } 32 | 33 | func New_Hook_Ping(a *Acc) func(...any) error { 34 | return func(...any) error { 35 | _, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 36 | Tag: `iq`, 37 | Attrs: []*xmpp.KeyValue{ 38 | {Key: `id`, Value: a.Noise.NextIqId_1()}, 39 | {Key: `xmlns`, Value: `w:p`}, 40 | {Key: `type`, Value: `get`}, 41 | {Key: `to`, Value: `s.whatsapp.net`, Type: 1}, 42 | }, 43 | Children: []*xmpp.Node{ 44 | { 45 | Tag: `ping`, 46 | }, 47 | }, 48 | }) 49 | return e 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /signal/ecc/djb_ec_public.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | import "wa/signal/util/bytehelper" 4 | 5 | // NewDjbECPublicKey creates a new Curve25519 public key with the given bytes. 6 | func NewDjbECPublicKey(publicKey []byte) *DjbECPublicKey { 7 | len_ := len(publicKey) 8 | 9 | var pub32 []byte 10 | 11 | if len_ == 32 { 12 | pub32 = publicKey 13 | } else if len_ == 33 && publicKey[0] == DjbType { 14 | pub32 = publicKey[1:] 15 | } else { 16 | return nil 17 | } 18 | key := DjbECPublicKey{ 19 | publicKey: bytehelper.SliceToArray(pub32), 20 | } 21 | return &key 22 | } 23 | 24 | // DjbECPublicKey implements the ECPublicKey interface and uses Curve25519. 25 | type DjbECPublicKey struct { 26 | publicKey [32]byte 27 | } 28 | 29 | // PublicKey returns the EC public key as a byte array. 30 | func (d *DjbECPublicKey) PublicKey() [32]byte { 31 | return d.publicKey 32 | } 33 | 34 | // Serialize returns the public key prepended by the DjbType value. 35 | func (d *DjbECPublicKey) Serialize() []byte { 36 | return append([]byte{DjbType}, d.publicKey[:]...) 37 | } 38 | 39 | // Type returns the DjbType value. 40 | func (d *DjbECPublicKey) Type() int { 41 | return DjbType 42 | } 43 | -------------------------------------------------------------------------------- /core/security.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/xmpp" 6 | 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func (c Core) Set2fa(j *ajson.Json) *ajson.Json { 11 | a, e := GetAccFromJson(j) 12 | if e != nil { 13 | return NewErrRet(e) 14 | } 15 | 16 | code, e := j.Get(`code`).TryString() 17 | if e != nil { 18 | return NewErrRet(errors.New(`missing 'code'`)) 19 | } 20 | email := j.Get(`email`).String() 21 | 22 | ch := []*xmpp.Node{ 23 | { 24 | Tag: `code`, 25 | Data: []byte(code), 26 | }, 27 | } 28 | if code != `` { 29 | ch = append(ch, &xmpp.Node{ 30 | Tag: `email`, 31 | Data: []byte(email), 32 | }) 33 | } 34 | 35 | nr, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 36 | Tag: `iq`, 37 | Attrs: []*xmpp.KeyValue{ 38 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 39 | {Key: `to`, Value: `s.whatsapp.net`}, 40 | {Key: `type`, Value: `set`}, 41 | {Key: `xmlns`, Value: `urn:xmpp:whatsapp:account`}, 42 | }, 43 | Children: []*xmpp.Node{ 44 | { 45 | Tag: `2fa`, 46 | Children: ch, 47 | }, 48 | }, 49 | }) 50 | if e != nil { 51 | return NewErrRet(e) 52 | } 53 | 54 | return NewJsonRet(nr.ToJson()) 55 | } 56 | -------------------------------------------------------------------------------- /signal/state/store/identity.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "wa/signal/keys/identity" 5 | "wa/signal/protocol" 6 | ) 7 | 8 | // IdentityKey provides an interface to identity information. 9 | type IdentityKey interface { 10 | // Get the local client's identity key pair. 11 | GetIdentityKeyPair() (*identity.KeyPair, error) 12 | 13 | // Return the local client's registration ID. 14 | // 15 | // Clients should maintain a registration ID, a random number between 1 and 16380 16 | // that's generated once at install time. 17 | GetLocalRegistrationId() (uint32, error) 18 | 19 | // Save a remote client's identity key in our identity store. 20 | SaveIdentity(address *protocol.SignalAddress, identityKey *identity.Key) error 21 | 22 | // Verify a remote client's identity key. 23 | // 24 | // Determine whether a remote client's identity is trusted. Trust is based on 25 | // 'trust on first use'. This means that an identity key is considered 'trusted' 26 | // if there is no entry for the recipient in the local store, or if it matches the 27 | // saved key for a recipient in the local store. 28 | IsTrustedIdentity(address *protocol.SignalAddress, identityKey *identity.Key) bool 29 | } 30 | -------------------------------------------------------------------------------- /core/dirty.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "wa/xmpp" 5 | ) 6 | 7 | func (a *Acc) set_dirty_clean(type_ string) error { 8 | _, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 9 | Tag: `iq`, 10 | Attrs: []*xmpp.KeyValue{ 11 | {Key: `id`, Value: a.Noise.NextIqId_1()}, 12 | {Key: `to`, Value: `s.whatsapp.net`}, 13 | {Key: `type`, Value: `set`}, 14 | {Key: `xmlns`, Value: `urn:xmpp:whatsapp:dirty`}, 15 | }, 16 | Children: []*xmpp.Node{ 17 | { 18 | Tag: `clean`, 19 | Attrs: []*xmpp.KeyValue{ 20 | {Key: `timestamp`, Value: `0`}, 21 | {Key: `type`, Value: type_}, 22 | }, 23 | }, 24 | }, 25 | }) 26 | return e 27 | } 28 | func New_Hook_Dirty(a *Acc) func(...any) error { 29 | return func(args ...any) error { 30 | n := args[0].(*xmpp.Node) 31 | 32 | // if contains child `dirty` 33 | if ch, ok := n.FindChildByTag(`dirty`); ok { 34 | type_, _ := ch.GetAttr(`type`) 35 | 36 | if type_ == `groups` { 37 | a.Log.Warning(`groups dirty`) 38 | return a.Store.SetGroupsDirty() 39 | } 40 | if type_ == `account_sync` { 41 | 42 | a.Log.Warning(`account_sync dirty`) 43 | return a.Store.SetAccountSyncDirty() 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /db/log_cfg_debug.go: -------------------------------------------------------------------------------- 1 | //go:build !production 2 | // +build !production 3 | 4 | package db 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "log" 10 | "time" 11 | 12 | "github.com/fatih/color" 13 | "github.com/mattn/go-colorable" 14 | "go.mongodb.org/mongo-driver/bson" 15 | ) 16 | 17 | var writer io.Writer 18 | 19 | func init() { 20 | writer = io.MultiWriter(colorable.NewColorableStdout()) 21 | log.SetOutput(writer) 22 | } 23 | 24 | // run on my self kali 25 | func (l *Logger) DoLog(level int, format string, args ...interface{}) { 26 | if level >= LogLevel { 27 | switch level { 28 | case DEBUG: 29 | log.Printf(color.HiWhiteString(format), args...) 30 | case INFO: 31 | log.Printf(color.HiBlueString(format), args...) 32 | case SUCCESS: 33 | log.Printf(color.HiGreenString(format), args...) 34 | case WARNING: 35 | log.Printf(color.HiYellowString(format), args...) 36 | case ERROR: 37 | log.Printf(color.HiRedString(format), args...) 38 | case BUG: 39 | log.Printf(color.HiRedString(format), args...) 40 | } 41 | } 42 | // log to db 43 | if level >= LogLevel { 44 | bs := bson.M{ 45 | `Time`: time.Now(), // for TTL 46 | `AccId`: l.AccId, 47 | `Level`: level, 48 | `Text`: fmt.Sprintf(format, args...), 49 | } 50 | colLog.InsertOne(ctx, bs) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /signal/groups/ratchet/sender_chain_key.go: -------------------------------------------------------------------------------- 1 | package ratchet 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | 7 | "wa/signal/pb" 8 | ) 9 | 10 | var messageKeySeed = []byte{0x01} 11 | var chainKeySeed = []byte{0x02} 12 | 13 | // NewSenderChainKey will return a new SenderChainKey. 14 | func NewSenderChainKey(iteration uint32, chainKey []byte) *SenderChainKey { 15 | return &SenderChainKey{ 16 | P: &pb.SenderKeyStateStructure_SenderChainKey{ 17 | Iteration: &iteration, 18 | Seed: chainKey, 19 | }, 20 | } 21 | } 22 | 23 | type SenderChainKey struct { 24 | P *pb.SenderKeyStateStructure_SenderChainKey 25 | } 26 | 27 | func (k *SenderChainKey) Iteration() uint32 { 28 | return k.P.GetIteration() 29 | } 30 | 31 | func (k *SenderChainKey) SenderMessageKey() (*SenderMessageKey, error) { 32 | return NewSenderMessageKey(k.P.GetIteration(), k.getDerivative(messageKeySeed, k.P.GetSeed())) 33 | } 34 | 35 | func (k *SenderChainKey) Next() *SenderChainKey { 36 | return NewSenderChainKey(k.P.GetIteration()+1, k.getDerivative(chainKeySeed, k.P.GetSeed())) 37 | } 38 | 39 | func (k *SenderChainKey) Seed() []byte { 40 | return k.P.GetSeed() 41 | } 42 | 43 | func (k *SenderChainKey) getDerivative(seed []byte, key []byte) []byte { 44 | mac := hmac.New(sha256.New, key[:]) 45 | mac.Write(seed) 46 | 47 | return mac.Sum(nil) 48 | } 49 | -------------------------------------------------------------------------------- /signal/keys/session/pair.go: -------------------------------------------------------------------------------- 1 | // Package session provides a simple structure for session keys, which is 2 | // a pair of root and chain keys for a session. 3 | package session 4 | 5 | import ( 6 | "wa/signal/ecc" 7 | "wa/signal/keys/chain" 8 | "wa/signal/keys/message" 9 | ) 10 | 11 | // RootKeyable is an interface for all root key implementations that are part of 12 | // a session keypair. 13 | type RootKeyable interface { 14 | Bytes() []byte 15 | CreateChain(theirRatchetKey ecc.ECPublicKeyable, ourRatchetKey *ecc.ECKeyPair) (*KeyPair, error) 16 | } 17 | 18 | // ChainKeyable is an interface for all chain key implementations that are part of 19 | // a session keypair. 20 | type ChainKeyable interface { 21 | Key() []byte 22 | Index() uint32 23 | NextKey() *chain.Key 24 | MessageKeys() *message.Keys 25 | Current() *chain.Key 26 | } 27 | 28 | // NewKeyPair returns a new session key pair that holds a root and chain key. 29 | func NewKeyPair(rootKey RootKeyable, chainKey ChainKeyable) *KeyPair { 30 | keyPair := KeyPair{ 31 | RootKey: rootKey, 32 | ChainKey: chainKey, 33 | } 34 | 35 | return &keyPair 36 | } 37 | 38 | // KeyPair is a session key pair that holds a single root and chain key pair. These 39 | // keys are ratcheted after every message sent and every message round trip. 40 | type KeyPair struct { 41 | RootKey RootKeyable 42 | ChainKey ChainKeyable 43 | } 44 | -------------------------------------------------------------------------------- /def/tls.go: -------------------------------------------------------------------------------- 1 | package def 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | 7 | "arand" 8 | ) 9 | 10 | // Android 7: 9fc6ef6efc99b933c5e2d8fcf4f68955 11 | var Ja3_Emu = `771,49195-49196-52393-49199-49200-52392-158-159-49161-49162-49171-49172-51-57-156-157-47-53,65281-0-23-35-13-16-11-10,23-24-25,0` 12 | 13 | // Android 8/9: d8c87b9bfde38897979e41242626c2f3 14 | var Ja3_WhiteMi6x = `771,49195-49196-52393-49199-49200-52392-49161-49162-49171-49172-156-157-47-53,65281-0-23-35-13-5-16-11-10,29-23-24,0` 15 | 16 | // Android 10/11: 9b02ebd3a43b62d825e1ac605b621dc8 17 | var Ja3_Android_10 = `771,4865-4866-4867-49195-49196-52393-49199-49200-52392-49161-49162-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-51-45-43-21,29-23-24,0` 18 | 19 | const ( 20 | Ja3Type_Default int8 = 1 // the default, White Mi6x 21 | Ja3Type_RandomGen int8 = 2 22 | Ja3Type_Custom int8 = 3 23 | ) 24 | 25 | func NewRandomJa3Config() string { 26 | parts := strings.Split(Ja3_WhiteMi6x, ",") 27 | extension_str := parts[1] 28 | 29 | extensions := strings.Split(extension_str, "-") 30 | 31 | fixed := extensions[0:8] // fixed size: 8 32 | rest := extensions[8:] 33 | 34 | for _, r := range rest { 35 | if arand.Bool() { 36 | fixed = append(fixed, r) 37 | } 38 | } 39 | 40 | rand.Shuffle(len(fixed), func(i, j int) { fixed[i], fixed[j] = fixed[j], fixed[i] }) 41 | parts[1] = strings.Join(fixed, "-") 42 | 43 | return strings.Join(parts, ",") 44 | } 45 | -------------------------------------------------------------------------------- /signal/keys/identity/key.go: -------------------------------------------------------------------------------- 1 | // Package identity provides identity keys used for verifying the identity 2 | // of a signal user. 3 | package identity 4 | 5 | import ( 6 | "encoding/hex" 7 | 8 | "wa/signal/ecc" 9 | "wa/signal/util/bytehelper" 10 | ) 11 | 12 | // NewKey generates a new IdentityKey from an ECPublicKey 13 | func NewKey(publicKey ecc.ECPublicKeyable) *Key { 14 | identityKey := Key{ 15 | publicKey: publicKey, 16 | } 17 | 18 | return &identityKey 19 | } 20 | 21 | // NewKeyFromBytes generates a new IdentityKey from public key bytes 22 | func NewKeyFromBytes(publicKey [32]byte) *Key { 23 | identityKey := Key{ 24 | publicKey: ecc.NewDjbECPublicKey(bytehelper.ArrayToSlice(publicKey)), 25 | } 26 | 27 | return &identityKey 28 | } 29 | 30 | // Key is a structure for representing an identity key. This same structure can 31 | // be used for verifying recipient's identity key or storing our own identity key. 32 | type Key struct { 33 | publicKey ecc.ECPublicKeyable 34 | } 35 | 36 | // Fingerprint gets the string fingerprint representation of the public key. 37 | func (k *Key) Fingerprint() string { 38 | return hex.EncodeToString(k.publicKey.Serialize()) 39 | } 40 | 41 | // PublicKey returns the EC Public key of the identity key 42 | func (k *Key) PublicKey() ecc.ECPublicKeyable { 43 | return k.publicKey 44 | } 45 | 46 | // Serialize returns the serialized version of the key 47 | func (k *Key) Serialize() []byte { 48 | return k.publicKey.Serialize() 49 | } 50 | -------------------------------------------------------------------------------- /signal/pb/SenderMessageKey.proto: -------------------------------------------------------------------------------- 1 | package pb; 2 | option go_package = ".;pb"; 3 | 4 | option java_package = "org.whispersystems.libsignal.protocol"; 5 | option java_outer_classname = "SignalProtos"; 6 | 7 | message SignalMessage { 8 | optional bytes ratchetKey = 1; 9 | optional uint32 counter = 2; 10 | optional uint32 previousCounter = 3; 11 | optional bytes ciphertext = 4; 12 | } 13 | 14 | message PreKeySignalMessage { 15 | optional uint32 preKeyId = 1; 16 | optional bytes baseKey = 2; 17 | optional bytes identityKey = 3; 18 | optional bytes message = 4; // SignalMessage 19 | optional uint32 registrationId = 5; 20 | optional uint32 signedPreKeyId = 6; 21 | } 22 | 23 | message KeyExchangeMessage { 24 | optional uint32 id = 1; 25 | optional bytes baseKey = 2; 26 | optional bytes ratchetKey = 3; 27 | optional bytes identityKey = 4; 28 | optional bytes baseKeySignature = 5; 29 | } 30 | 31 | message SenderKeyMessage { 32 | optional uint32 id = 1; 33 | optional uint32 iteration = 2; 34 | optional bytes ciphertext = 3; 35 | } 36 | 37 | message SenderKeyDistributionMessage { 38 | optional uint32 id = 1; 39 | optional uint32 iteration = 2; 40 | optional bytes chainKey = 3; 41 | optional bytes signingKey = 4; 42 | } 43 | 44 | message DeviceConsistencyCodeMessage { 45 | optional uint32 generation = 1; 46 | optional bytes signature = 2; 47 | } -------------------------------------------------------------------------------- /signal/pb/WhisperTextProtocol.proto: -------------------------------------------------------------------------------- 1 | package pb; 2 | option go_package = ".;pb"; 3 | 4 | option java_package = "org.whispersystems.libsignal.protocol"; 5 | option java_outer_classname = "SignalProtos"; 6 | 7 | message SignalMessage { 8 | optional bytes ratchetKey = 1; 9 | optional uint32 counter = 2; 10 | optional uint32 previousCounter = 3; 11 | optional bytes ciphertext = 4; 12 | } 13 | 14 | message PreKeySignalMessage { 15 | optional uint32 preKeyId = 1; 16 | optional bytes baseKey = 2; 17 | optional bytes identityKey = 3; 18 | optional bytes message = 4; // SignalMessage 19 | optional uint32 registrationId = 5; 20 | optional uint32 signedPreKeyId = 6; 21 | } 22 | 23 | message KeyExchangeMessage { 24 | optional uint32 id = 1; 25 | optional bytes baseKey = 2; 26 | optional bytes ratchetKey = 3; 27 | optional bytes identityKey = 4; 28 | optional bytes baseKeySignature = 5; 29 | } 30 | 31 | message SenderKeyMessage { 32 | optional uint32 id = 1; 33 | optional uint32 iteration = 2; 34 | optional bytes ciphertext = 3; 35 | } 36 | 37 | message SenderKeyDistributionMessage { 38 | optional uint32 id = 1; 39 | optional uint32 iteration = 2; 40 | optional bytes chainKey = 3; 41 | optional bytes signingKey = 4; 42 | } 43 | 44 | message DeviceConsistencyCodeMessage { 45 | optional uint32 generation = 1; 46 | optional bytes signature = 2; 47 | } -------------------------------------------------------------------------------- /wam/chunk.go: -------------------------------------------------------------------------------- 1 | package wam 2 | 3 | import "bytes" 4 | 5 | type Chunk interface { 6 | Append(*Record) 7 | } 8 | 9 | // Wild 10 | type WildChunk struct { 11 | Records []*Record 12 | } 13 | 14 | func (c *WildChunk) Append(id int32, val interface{}) { 15 | c.Records = append(c.Records, &Record{ 16 | ClassType: 0, 17 | Id: id, 18 | Value: val, 19 | }) 20 | } 21 | 22 | func (c *WildChunk) ToBytes() ([]byte, error) { 23 | bs := []byte{} 24 | for _, rec := range c.Records { 25 | b, e := rec.ToBytes() 26 | if e != nil { 27 | return nil, e 28 | } 29 | bs = append(bs, b...) 30 | } 31 | return bs, nil 32 | } 33 | 34 | // Class 35 | type ClassChunk struct { 36 | Id int32 37 | Value interface{} 38 | Records []*Record 39 | } 40 | 41 | func (c *ClassChunk) Append(id int32, val interface{}) { 42 | c.Records = append(c.Records, &Record{ 43 | ClassType: 2, 44 | Id: id, 45 | Value: val, 46 | }) 47 | } 48 | 49 | func (c *ClassChunk) ToBytes() ([]byte, error) { 50 | bs := [][]byte{} 51 | // append first class id rec 52 | frec := Record{ 53 | ClassType: 1, 54 | Id: c.Id, 55 | Value: c.Value, 56 | } 57 | b, e := frec.ToBytes() 58 | if e != nil { 59 | return nil, e 60 | } 61 | bs = append(bs, b) 62 | for _, rec := range c.Records { 63 | b, e := rec.ToBytes() 64 | if e != nil { 65 | return nil, e 66 | } 67 | bs = append(bs, b) 68 | } 69 | last := bs[len(bs)-1] 70 | last[0] |= 4 71 | return bytes.Join(bs, []byte{}), nil 72 | } 73 | -------------------------------------------------------------------------------- /signal/kdf/hkdf.go: -------------------------------------------------------------------------------- 1 | // Package kdf provides a key derivation function to calculate key output 2 | // and negotiate shared secrets for curve X25519 keys. 3 | package kdf 4 | 5 | import ( 6 | "crypto/sha256" 7 | "io" 8 | 9 | "golang.org/x/crypto/curve25519" 10 | "golang.org/x/crypto/hkdf" 11 | ) 12 | 13 | // HKDF is a hashed key derivation function type that can be used to derive keys. 14 | type HKDF func(inputKeyMaterial, salt, info []byte, outputLength int) ([]byte, error) 15 | 16 | // DeriveSecrets derives the requested number of bytes using HKDF with the given 17 | // input, salt, and info. 18 | func DeriveSecrets(inputKeyMaterial, salt, info []byte, outputLength int) ([]byte, error) { 19 | kdf := hkdf.New(sha256.New, inputKeyMaterial, salt, info) 20 | 21 | secrets := make([]byte, outputLength) 22 | length, err := io.ReadFull(kdf, secrets) 23 | if err != nil { 24 | return nil, err 25 | } 26 | if length != outputLength { 27 | return nil, err 28 | } 29 | 30 | return secrets, nil 31 | } 32 | 33 | // CalculateSharedSecret uses DH Curve25519 to find a shared secret. The result of this function 34 | // should be used in `DeriveSecrets` to output the Root and Chain keys. 35 | func CalculateSharedSecret(theirKey, ourKey [32]byte) [32]byte { 36 | var sharedSecret [32]byte 37 | curve25519.ScalarMult(&sharedSecret, &ourKey, &theirKey) 38 | 39 | return sharedSecret 40 | } 41 | 42 | // KeyMaterial is a structure for representing a cipherkey, mac, and iv 43 | type KeyMaterial struct { 44 | CipherKey []byte 45 | MacKey []byte 46 | IV []byte 47 | } 48 | -------------------------------------------------------------------------------- /signal/fingerprint/display.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import ( 4 | "fmt" 5 | 6 | "wa/signal/util/bytehelper" 7 | ) 8 | 9 | // NewDisplay will return a new displayable fingerprint. 10 | func NewDisplay(localFingerprint, remoteFingerprint []byte) *Display { 11 | return &Display{ 12 | localFingerprintNumbers: displayStringFor(localFingerprint), 13 | remoteFingerprintNumbers: displayStringFor(remoteFingerprint), 14 | } 15 | } 16 | 17 | // Display is a structure for displayable fingerprints. 18 | type Display struct { 19 | localFingerprintNumbers string 20 | remoteFingerprintNumbers string 21 | } 22 | 23 | // DisplayText will return a string of the fingerprint numbers. 24 | func (d *Display) DisplayText() string { 25 | if d.localFingerprintNumbers < d.remoteFingerprintNumbers { 26 | return d.localFingerprintNumbers + d.remoteFingerprintNumbers 27 | } 28 | return d.remoteFingerprintNumbers + d.localFingerprintNumbers 29 | } 30 | 31 | // displayStringFor will return a displayable string representation 32 | // of the given fingerprint. 33 | func displayStringFor(fingerprint []byte) string { 34 | return encodedChunk(fingerprint, 0) + 35 | encodedChunk(fingerprint, 5) + 36 | encodedChunk(fingerprint, 10) + 37 | encodedChunk(fingerprint, 15) + 38 | encodedChunk(fingerprint, 20) + 39 | encodedChunk(fingerprint, 25) 40 | } 41 | 42 | // encodedChunk will return an encoded string of the given hash. 43 | func encodedChunk(hash []byte, offset int) string { 44 | chunk := bytehelper.Bytes5ToInt64(hash, offset) % 100000 45 | return fmt.Sprintf("%05d", chunk) 46 | } 47 | -------------------------------------------------------------------------------- /db/log.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "go.mongodb.org/mongo-driver/bson" 5 | "go.mongodb.org/mongo-driver/mongo" 6 | "go.mongodb.org/mongo-driver/mongo/options" 7 | ) 8 | 9 | var LogLevel = ERROR 10 | 11 | var colLog *mongo.Collection 12 | 13 | func init() { 14 | colLog = client.Database(DB_NAME).Collection(`Log`) 15 | { 16 | _, e := colLog.Indexes().CreateOne(ctx, mongo.IndexModel{ 17 | Keys: bson.M{"AccId": 1}, 18 | }) 19 | 20 | if e != nil { 21 | panic(`fail create db index`) 22 | } 23 | } 24 | { // expire after 30 days 25 | _, e := colLog.Indexes().CreateOne(ctx, mongo.IndexModel{ 26 | Keys: bson.M{"Time": 1}, 27 | Options: options.Index().SetExpireAfterSeconds(3600 * 24 * 30), 28 | }) 29 | if e != nil { 30 | panic(`fail create db index`) 31 | } 32 | } 33 | } 34 | 35 | const ( 36 | DEBUG = iota 37 | INFO 38 | SUCCESS 39 | WARNING 40 | ERROR 41 | BUG 42 | ) 43 | 44 | type Logger struct { 45 | AccId uint64 46 | } 47 | 48 | func (l *Logger) Debug(format string, args ...interface{}) { 49 | l.DoLog(DEBUG, format, args...) 50 | } 51 | func (l *Logger) Info(format string, args ...interface{}) { 52 | l.DoLog(INFO, format, args...) 53 | } 54 | func (l *Logger) Success(format string, args ...interface{}) { 55 | l.DoLog(SUCCESS, format, args...) 56 | } 57 | func (l *Logger) Warning(format string, args ...interface{}) { 58 | l.DoLog(WARNING, format, args...) 59 | } 60 | func (l *Logger) Error(format string, args ...interface{}) { 61 | l.DoLog(ERROR, format, args...) 62 | } 63 | func (l *Logger) Bug(format string, args ...interface{}) { 64 | l.DoLog(BUG, format, args...) 65 | } 66 | -------------------------------------------------------------------------------- /noise/LICENSE: -------------------------------------------------------------------------------- 1 | Flynn® is a trademark of Prime Directive, Inc. 2 | 3 | Copyright (c) 2015 Prime Directive, Inc. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following disclaimer 13 | in the documentation and/or other materials provided with the 14 | distribution. 15 | * Neither the name of Prime Directive, Inc. nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "wa/db" 8 | "wa/def" 9 | "wa/rpc" 10 | "wa/rpc/pb" 11 | 12 | "aconfig" 13 | 14 | "github.com/fatih/color" 15 | "google.golang.org/grpc" 16 | 17 | "net/http" 18 | _ "net/http/pprof" 19 | ) 20 | 21 | var BuildTime string 22 | 23 | type Config struct { 24 | LogLevel int 25 | Pprof bool 26 | Port int 27 | } 28 | 29 | var cfg_fn = "server.toml" 30 | 31 | func main() { 32 | fmt.Println("Build Time: " + color.HiBlueString(BuildTime)) 33 | 34 | color.HiBlue(`loading config %s`, cfg_fn) 35 | cfg := &Config{ 36 | LogLevel: -1, 37 | Pprof: false, 38 | Port: 3423, 39 | } 40 | aconfig.Load(cfg_fn, cfg) 41 | aconfig.Save(cfg_fn, cfg) 42 | 43 | db.LogLevel = cfg.LogLevel 44 | color.HiBlue(`set LogLevel to %d`, db.LogLevel) 45 | 46 | if cfg.Pprof { 47 | go func() { 48 | color.HiYellow("Pprof : http://localhost:7788/debug/pprof") 49 | e := http.ListenAndServe("0.0.0.0:7788", nil) 50 | if e != nil { 51 | panic(e) 52 | } 53 | }() 54 | } 55 | 56 | def.RpcPort = cfg.Port 57 | listen() 58 | } 59 | 60 | func listen() { 61 | lis, err := net.Listen("tcp", fmt.Sprintf(":%d", def.RpcPort)) 62 | if err != nil { 63 | color.HiRed("failed to listen: %v", err) 64 | os.Exit(1) 65 | } 66 | defer lis.Close() 67 | sv := &rpc.WaServer{} 68 | s := grpc.NewServer() 69 | pb.RegisterRpcServer(s, sv) 70 | 71 | color.White("Server version: %s + %s", 72 | def.VERSION(false), def.VERSION((true))) 73 | color.HiGreen("Server running at %d", def.RpcPort) 74 | 75 | if err := s.Serve(lis); err != nil { 76 | color.HiRed("failed to serve: %v", err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tools/jpg_2_jfif/jfif.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os/exec" 7 | "syscall" 8 | ) 9 | 10 | const defaultFailedCode = -3423 11 | 12 | func RunCommand(dir, name string, args ...string) (stdout string, stderr string, exitCode int) { 13 | var outbuf, errbuf bytes.Buffer 14 | cmd := exec.Command(name, args...) 15 | cmd.Stdout = &outbuf 16 | cmd.Stderr = &errbuf 17 | if len(dir) > 0 { 18 | cmd.Dir = dir 19 | } 20 | 21 | err := cmd.Run() 22 | stdout = outbuf.String() 23 | stderr = errbuf.String() 24 | 25 | if err != nil { 26 | // try to get the exit code 27 | if exitError, ok := err.(*exec.ExitError); ok { 28 | ws := exitError.Sys().(syscall.WaitStatus) 29 | exitCode = ws.ExitStatus() 30 | } else { 31 | // This will happen (in OSX) if `name` is not available in $PATH, 32 | // in this situation, exit code could not be get, and stderr will be 33 | // empty string very likely, so we use the default fail code, and format err 34 | // to string and set to stderr 35 | exitCode = defaultFailedCode 36 | if stderr == "" { 37 | stderr = err.Error() 38 | } 39 | } 40 | } else { 41 | // success, exitCode should be 0 if go is ok 42 | ws := cmd.ProcessState.Sys().(syscall.WaitStatus) 43 | exitCode = ws.ExitStatus() 44 | } 45 | return 46 | } 47 | 48 | func convert(exif, jfif string) bool { 49 | stdout, stderr, exit_code := RunCommand(".", "python3", "convert.py", exif, jfif) 50 | _ = stdout 51 | _ = stderr 52 | //fmt.Println(stdout, stderr) 53 | return exit_code == 0 54 | } 55 | 56 | func main() { 57 | exif := "/e/jpg.jpg" 58 | jfif := "/e/jfif.jfif" 59 | 60 | err := convert(exif, jfif) 61 | fmt.Println(err) 62 | } 63 | -------------------------------------------------------------------------------- /tools/wam_decoder/wam_decoder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "time" 8 | 9 | "ahex" 10 | "wa/wam" 11 | 12 | "github.com/fatih/color" 13 | ) 14 | 15 | func main() { 16 | var bs []byte 17 | if len(os.Args) == 2 { 18 | bs = ahex.Dec(os.Args[1]) 19 | } else { 20 | color.HiRed("usage: wam 010203....") 21 | os.Exit(1) 22 | } 23 | 24 | recs, e := wam.Parse(bs) 25 | if e != nil { 26 | color.HiRed(e.Error()) 27 | os.Exit(1) 28 | } 29 | 30 | var cur_fid int32 = 0 31 | 32 | for _, rec := range recs { 33 | idStr := color.HiBlueString("%d", rec.Id) 34 | 35 | if rec.ClassType == 1 { // class begin 36 | fmt.Println(``) 37 | if ms, ok := wam.MapStats[rec.Id]; ok { 38 | idStr = color.HiGreenString("%d\t(%s)", rec.Id, ms.Desc) 39 | } 40 | 41 | cur_fid = rec.Id 42 | } else if fa, ok := wam.MapStats[cur_fid]; ok { 43 | if statName, ok := fa.Stats[rec.Id]; ok { 44 | idStr += color.HiBlueString("\t(%s)", statName) 45 | } 46 | } 47 | 48 | valStr := color.HiYellowString("%v", rec.Value) 49 | 50 | valType := `nil` 51 | if rec.Value != nil { 52 | valType = reflect.TypeOf(rec.Value).String() 53 | if cur_fid == 0 && rec.Id == 47 { 54 | if val, ok := rec.Value.(int32); ok { 55 | tm := time.Unix(int64(val), 0) 56 | valStr += fmt.Sprintf(" (%s)", tm.Format("2006-01-02 15:04:05")) 57 | } 58 | } 59 | } 60 | 61 | all := fmt.Sprintf("%d %s %s %s ", 62 | rec.ClassType, 63 | idStr, 64 | valStr, 65 | valType, 66 | ) 67 | if rec.IsClassEnd { // chunk End 68 | all += color.HiMagentaString("<- chunk end\n") 69 | cur_fid = 0 70 | } 71 | 72 | fmt.Println(all) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pb_gen/pb_gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "str" 10 | 11 | "github.com/fatih/color" 12 | ) 13 | 14 | func main() { 15 | mm_path, _ := os.Getwd() 16 | 17 | target_proto_dir := mm_path + "/pb" 18 | 19 | // 1. clean up files not *.pb.cc|*.pb.h 20 | color.HiYellow("cleanup %s ...", target_proto_dir) 21 | 22 | var files []string 23 | 24 | err := filepath.Walk(target_proto_dir, 25 | func(path string, info os.FileInfo, err error) error { 26 | if err != nil { 27 | return err 28 | } 29 | 30 | if filepath.Ext(path) == ".proto" { 31 | // save to files 32 | files = append(files, path) 33 | } else if str.ContainsAny(path, ".pb.h", ".pb.cc", ".pb.go") { 34 | os.Remove(path) 35 | return nil 36 | } 37 | // fmt.Println(path, info.Size()) 38 | return nil 39 | }) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | //fmt.Println(files) 45 | //os.Exit(1) 46 | 47 | // 2. generate 48 | color.HiYellow("compiling .proto in %s ...", target_proto_dir) 49 | 50 | protoc := `protoc` 51 | 52 | for _, f := range files { 53 | 54 | var cmds []string 55 | // append this file path 56 | cmds = append(cmds, fmt.Sprintf(`--proto_path=%s`, filepath.Dir(f))) 57 | 58 | cmds = append(cmds, fmt.Sprintf(`--proto_path=%s`, mm_path+"/pb")) 59 | cmds = append(cmds, fmt.Sprintf(`--go_out=%s`, filepath.Dir(f))) 60 | cmds = append(cmds, fmt.Sprintf(`%s`, f)) 61 | 62 | runner := exec.Command(protoc, cmds...) 63 | runner.Stdout = os.Stdout 64 | runner.Stderr = os.Stderr 65 | err := runner.Run() 66 | if err != nil { 67 | color.HiRed(err.Error()) 68 | os.Exit(1) 69 | } 70 | } 71 | 72 | color.HiGreen("Done") 73 | } 74 | -------------------------------------------------------------------------------- /signal/groups/ratchet/sender_message_key.go: -------------------------------------------------------------------------------- 1 | package ratchet 2 | 3 | import ( 4 | "wa/signal/kdf" 5 | "wa/signal/pb" 6 | "wa/signal/util/bytehelper" 7 | ) 8 | 9 | // KdfInfo is optional bytes to include in deriving secrets with KDF. 10 | const KdfInfo string = "WhisperGroup" 11 | 12 | // NewSenderMessageKey will return a new sender message key using the given 13 | // iteration and seed. 14 | func NewSenderMessageKey(iteration uint32, seed []byte) (*SenderMessageKey, error) { 15 | derivative, err := kdf.DeriveSecrets(seed, nil, []byte(KdfInfo), 48) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | // Split our derived secrets into 2 parts 21 | parts := bytehelper.Split(derivative, 16, 32) 22 | 23 | // Build the message key. 24 | senderKeyMessage := &SenderMessageKey{ 25 | P: &pb.SenderKeyStateStructure_SenderMessageKey{ 26 | Iteration: &iteration, 27 | Seed: seed, 28 | }, 29 | iv: parts[0], 30 | cipherKey: parts[1], 31 | } 32 | 33 | return senderKeyMessage, nil 34 | } 35 | 36 | // SenderMessageKey is a structure for sender message keys used in group messaging. 37 | type SenderMessageKey struct { 38 | P *pb.SenderKeyStateStructure_SenderMessageKey 39 | 40 | iv []byte 41 | cipherKey []byte 42 | } 43 | 44 | // Iteration will return the sender message key's iteration. 45 | func (k *SenderMessageKey) Iteration() uint32 { 46 | return k.P.GetIteration() 47 | } 48 | 49 | // Iv will return the sender message key's initialization vector. 50 | func (k *SenderMessageKey) Iv() []byte { 51 | return k.iv 52 | } 53 | 54 | // CipherKey will return the key in bytes. 55 | func (k *SenderMessageKey) CipherKey() []byte { 56 | return k.cipherKey 57 | } 58 | 59 | // Seed will return the sender message key's seed. 60 | func (k *SenderMessageKey) Seed() []byte { 61 | return k.P.GetSeed() 62 | } 63 | -------------------------------------------------------------------------------- /core/call.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "wa/xmpp" 5 | ) 6 | 7 | func New_Hook_CallOffer(a *Acc) func(...any) error { 8 | // Decrypt and Replace node.Data 9 | return func(args ...any) error { 10 | n := args[0].(*xmpp.Node) 11 | 12 | from, _ := n.GetAttr(`from`) 13 | id, _ := n.GetAttr(`id`) 14 | 15 | if len(n.Children) == 0 { 16 | return nil 17 | } 18 | ch0 := n.Children[0] 19 | if ch0.Tag != `offer` { 20 | return nil 21 | } 22 | 23 | ch0a := ch0.MapAttrs() 24 | call_creator, ok1 := ch0a[`call-creator`] 25 | call_id, ok2 := ch0a[`call-id`] 26 | if !ok1 || !ok2 { 27 | return nil 28 | } 29 | 30 | a.Noise.WriteXmppNode(&xmpp.Node{ 31 | Tag: `receipt`, 32 | Attrs: []*xmpp.KeyValue{ 33 | {Key: `id`, Value: id}, 34 | {Key: `to`, Value: from}, 35 | }, 36 | Children: []*xmpp.Node{ 37 | { 38 | Tag: ch0.Tag, 39 | Attrs: []*xmpp.KeyValue{ 40 | {Key: `call-creator`, Value: call_creator}, 41 | {Key: `call-id`, Value: call_id}, 42 | }, 43 | }, 44 | }, 45 | }) 46 | 47 | return nil 48 | } 49 | } 50 | 51 | func New_Hook_CallAck(a *Acc) func(...any) error { 52 | // Decrypt and Replace node.Data 53 | return func(args ...any) error { 54 | n := args[0].(*xmpp.Node) 55 | 56 | from, _ := n.GetAttr(`from`) 57 | id, _ := n.GetAttr(`id`) 58 | 59 | if len(n.Children) == 0 { 60 | return nil 61 | } 62 | ch0 := n.Children[0] 63 | 64 | switch ch0.Tag { 65 | case `relaylatency`: 66 | case `terminate`: 67 | default: 68 | return nil 69 | } 70 | 71 | a.Noise.WriteXmppNode(&xmpp.Node{ 72 | Tag: `ack`, 73 | Attrs: []*xmpp.KeyValue{ 74 | {Key: `class`, Value: `call`}, 75 | {Key: `to`, Value: from}, 76 | {Key: `id`, Value: id}, 77 | {Key: `type`, Value: ch0.Tag}, 78 | }, 79 | }) 80 | 81 | return nil 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /signal/state/record/state_pending_prekey.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/pb" 6 | "wa/signal/util/optional" 7 | 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // NewPendingPreKey will return a new pending pre key object. 12 | func NewPendingPreKey(preKeyID *optional.Uint32, signedPreKeyID uint32, 13 | baseKey ecc.ECPublicKeyable) *PendingPreKey { 14 | 15 | return &PendingPreKey{ 16 | preKeyID: preKeyID, 17 | signedPreKeyID: signedPreKeyID, 18 | baseKey: baseKey, 19 | } 20 | } 21 | 22 | // NewPendingPreKeyFromStruct will return a new pending prekey object from the 23 | // given structure. 24 | func NewPendingPreKeyFromStruct(preKey *pb.SessionStructure_PendingPreKey) (*PendingPreKey, error) { 25 | baseKey, err := ecc.DecodePoint(preKey.BaseKey, 0) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | id := optional.NewOptionalUint32(preKey.GetPreKeyId()) 31 | if preKey.PreKeyId == nil { 32 | id.IsEmpty = true 33 | } 34 | 35 | pendingPreKey := NewPendingPreKey( 36 | id, 37 | uint32(preKey.GetSignedPreKeyId()), 38 | baseKey, 39 | ) 40 | 41 | return pendingPreKey, nil 42 | } 43 | 44 | // PendingPreKey is a structure for pending pre keys 45 | // for a session state. 46 | type PendingPreKey struct { 47 | preKeyID *optional.Uint32 48 | signedPreKeyID uint32 49 | baseKey ecc.ECPublicKeyable 50 | } 51 | 52 | // structure will return a serializeable structure of the pending prekey. 53 | func (p *PendingPreKey) structure() *pb.SessionStructure_PendingPreKey { 54 | if p != nil { 55 | ret := &pb.SessionStructure_PendingPreKey{ 56 | SignedPreKeyId: proto.Int32(int32(p.signedPreKeyID)), 57 | BaseKey: p.baseKey.Serialize(), 58 | } 59 | if !p.preKeyID.IsEmpty { 60 | ret.PreKeyId = proto.Uint32(p.preKeyID.Value) 61 | } 62 | return ret 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /signal/state/record/prekey.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/pb" 6 | "wa/signal/util/bytehelper" 7 | "wa/signal/util/optional" 8 | 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | // NewPreKeyFromBytes will return a prekey record from the given bytes using the given serializer. 13 | func NewPreKeyFromBytes(serialized []byte) (*PreKey, error) { 14 | p := pb.PreKeyRecordStructure{} 15 | e := proto.Unmarshal(serialized, &p) 16 | if e != nil { 17 | return nil, e 18 | } 19 | return NewPreKeyFromStruct(&p) 20 | } 21 | 22 | func NewPreKeyFromStruct(structure *pb.PreKeyRecordStructure) (*PreKey, error) { 23 | preKey := &PreKey{ 24 | structure: structure, 25 | } 26 | 27 | // Generate the ECC key from bytes. 28 | publicKey := ecc.NewDjbECPublicKey(structure.PublicKey) 29 | privateKey := ecc.NewDjbECPrivateKey(bytehelper.SliceToArray(structure.PrivateKey)) 30 | keyPair := ecc.NewECKeyPair(publicKey, privateKey) 31 | preKey.keyPair = keyPair 32 | 33 | return preKey, nil 34 | } 35 | 36 | // NewPreKey record returns a new pre key record that can 37 | // be stored in a PreKeyStore. 38 | func NewPreKey(id uint32, keyPair *ecc.ECKeyPair) *PreKey { 39 | return &PreKey{ 40 | structure: &pb.PreKeyRecordStructure{ 41 | Id: proto.Uint32(id), 42 | PublicKey: keyPair.PublicKey().Serialize(), 43 | PrivateKey: bytehelper.ArrayToSlice(keyPair.PrivateKey().Serialize()), 44 | }, 45 | keyPair: keyPair, 46 | } 47 | } 48 | 49 | // PreKey record is a structure for storing pre keys inside 50 | // a PreKeyStore. 51 | type PreKey struct { 52 | structure *pb.PreKeyRecordStructure 53 | keyPair *ecc.ECKeyPair 54 | } 55 | 56 | // ID returns the pre key record's id. 57 | func (p *PreKey) ID() *optional.Uint32 { 58 | return optional.NewOptionalUint32(p.structure.GetId()) 59 | } 60 | 61 | // KeyPair returns the pre key record's key pair. 62 | func (p *PreKey) KeyPair() *ecc.ECKeyPair { 63 | return p.keyPair 64 | } 65 | 66 | // Serialize uses the PreKey serializer to return the PreKey 67 | // as serialized bytes. 68 | func (p *PreKey) Serialize() ([]byte, error) { 69 | return proto.Marshal(p.structure) 70 | } 71 | -------------------------------------------------------------------------------- /tools/xmpp_decoder/console/xmpp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "strings" 8 | 9 | "afs" 10 | "ahex" 11 | "algo" 12 | "wa/xmpp" 13 | ) 14 | 15 | func bytes_2_node(b []byte) (*xmpp.Node, error) { 16 | if len(b) < 2 { 17 | return nil, errors.New(`data too short: ` + ahex.Enc(b)) 18 | } 19 | if b[0] == 0 && b[1] == 0xf8 { 20 | b = b[1:] 21 | } else if b[0] == 2 && b[1] == 0x78 && b[2] == 0x9c { 22 | dec, e := algo.UnZlib(b[1:]) 23 | if e != nil { 24 | return nil, e 25 | } 26 | return bytes_2_node(dec) 27 | } 28 | r := xmpp.NewReader(b) 29 | 30 | return r.ReadNode() 31 | } 32 | 33 | func init() { 34 | flag.Parse() 35 | } 36 | 37 | func cmd_line_mode() { 38 | strs := []string{} 39 | for i := 0; i < flag.NArg(); i++ { 40 | strs = append(strs, flag.Arg(i)) 41 | } 42 | 43 | arg := strings.Join(strs, "") 44 | arg = strings.ReplaceAll(arg, "\n", "") 45 | arg = strings.ReplaceAll(arg, "\r", "") 46 | arg = strings.ReplaceAll(arg, "\t", "") 47 | arg = strings.ReplaceAll(arg, " ", "") 48 | 49 | if strings.HasPrefix(arg, "data:") { 50 | arg = arg[5:] 51 | } 52 | if strings.HasPrefix(arg, "result:") { 53 | arg = arg[7:] 54 | } 55 | 56 | n, e := bytes_2_node(ahex.Dec(arg)) 57 | if e != nil { 58 | panic(e) 59 | } 60 | fmt.Println(n.ToString()) 61 | } 62 | func file_mode() { 63 | lines := afs.ReadLines(flag.Arg(0)) 64 | new_lines := []string{} 65 | var prev_line string 66 | for _, line := range lines { 67 | new_lines = append(new_lines, line) 68 | if prev_line == "---- AesGcmWrap.Decrypt: ----" && strings.HasPrefix(line, "result:") { 69 | if n, e := bytes_2_node(ahex.Dec(line[7:])); e == nil { 70 | new_lines = append(new_lines, n.ToString()) 71 | } 72 | } else if prev_line == "---- AesGcmWrap.Encrypt: ----" && strings.HasPrefix(line, "data:") { 73 | if n, e := bytes_2_node(ahex.Dec(line[5:])); e == nil { 74 | new_lines = append(new_lines, n.ToString()) 75 | } 76 | } 77 | 78 | prev_line = line 79 | } 80 | 81 | f := strings.Join(new_lines, "\n") 82 | afs.Write(flag.Arg(0), []byte(f)) 83 | } 84 | 85 | func main() { 86 | if afs.Exist(flag.Arg(0)) { 87 | file_mode() 88 | } else { 89 | cmd_line_mode() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /signal/keys/root/key.go: -------------------------------------------------------------------------------- 1 | // Package root provides root keys which are used to derive new chain and 2 | // root keys in a ratcheting session. 3 | package root 4 | 5 | import ( 6 | "wa/signal/ecc" 7 | "wa/signal/kdf" 8 | "wa/signal/keys/chain" 9 | "wa/signal/keys/session" 10 | ) 11 | 12 | // DerivedSecretsSize is the size of the derived secrets for root keys. 13 | const DerivedSecretsSize = 64 14 | 15 | // KdfInfo is used as the info for message keys to derive secrets using a Key Derivation Function 16 | const KdfInfo string = "WhisperRatchet" 17 | 18 | // NewKey returns a new RootKey given the key derivation function and bytes. 19 | func NewKey(kdf kdf.HKDF, key []byte) *Key { 20 | rootKey := Key{ 21 | kdf: kdf, 22 | key: key, 23 | } 24 | 25 | return &rootKey 26 | } 27 | 28 | // Key is a structure for RootKeys, which are used to derive a new set of chain and root 29 | // keys for every round trip of messages. 30 | type Key struct { 31 | kdf kdf.HKDF 32 | key []byte 33 | } 34 | 35 | // Bytes returns the RootKey in bytes. 36 | func (k *Key) Bytes() []byte { 37 | return k.key 38 | } 39 | 40 | // CreateChain creates a new RootKey and ChainKey from the recipient's ratchet key and our private key. 41 | func (k *Key) CreateChain(theirRatchetKey ecc.ECPublicKeyable, ourRatchetKey *ecc.ECKeyPair) (*session.KeyPair, error) { 42 | theirPublicKey := theirRatchetKey.PublicKey() 43 | ourPrivateKey := ourRatchetKey.PrivateKey().Serialize() 44 | 45 | // Use our key derivation function to calculate a shared secret. 46 | sharedSecret := kdf.CalculateSharedSecret(theirPublicKey, ourPrivateKey) 47 | derivedSecretBytes, err := kdf.DeriveSecrets(sharedSecret[:], k.key, []byte(KdfInfo), DerivedSecretsSize) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | // Split the derived secret bytes in half, using one half for the root key and the second for the chain key. 53 | derivedSecrets := session.NewDerivedSecrets(derivedSecretBytes) 54 | 55 | // Create new root and chain key structures from the derived secrets. 56 | rootKey := NewKey(k.kdf, derivedSecrets.RootKey()) 57 | chainKey := chain.NewKey(k.kdf, derivedSecrets.ChainKey(), 0) 58 | 59 | // Create a session keypair with the generated root and chain keys. 60 | keyPair := session.NewKeyPair( 61 | rootKey, 62 | chainKey, 63 | ) 64 | 65 | return keyPair, nil 66 | } 67 | -------------------------------------------------------------------------------- /pb/NoiseHandshakeDevice.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | option go_package = ".;pb"; 4 | 5 | /* 6 | jadx for: 7 | non numeric portion of version name; 8 | 9 | search for this proto: 10 | 1. search for "connection_lc" 11 | 2. find: 12 | int connection_lc = v81.getInt("connection_lc", 0); 13 | v8_3.A02(); 14 | 4Ht v1_37 = (4Ht)v8_3.A00; 15 | v1_37.A01 |= 0x10000; 16 | v1_37.connection_lc = connection_lc; 17 | 3. x_ref for v1_37.connection_lc 18 | 19 | "chatd_connection: created IO streams; sessionId=" 20 | */ 21 | message NoiseHandshakeDevice { 22 | message _struct_5 { 23 | message Version { 24 | optional int32 V1 = 1; // 2 25 | optional int32 V2 = 2; // 20 26 | optional int32 V3 = 3; // 206 27 | optional int32 V4 = 4; // 22 28 | } 29 | optional int32 SMB_Android = 1; // 0:Personal new 2X6("ANDROID", 0, 0), 10:Biz new 28J("SMB_ANDROID", 10, 10) 30 | optional Version Ver = 2; 31 | optional string Mcc = 3; // 460 32 | optional string Mnc = 4; // 001 33 | optional string AndroidVersion = 5; // 9 34 | optional string Brand = 6; // Xiaomi 35 | optional string Product = 7; // wayne 36 | optional string Build = 8; // PKQ1.180904.001 37 | optional string Fdid = 9; 38 | optional int32 IsBeta = 10; // 1: beta, null:non-beta 39 | optional string Language = 11; // zh 40 | optional string Locale = 12; 41 | optional string Board = 13; 42 | } 43 | message DNS { 44 | optional int32 Config = 15; // 0, SYSTEM(0), GOOGLE(1), HARDCODED(2), OVERRIDE(3), FALLBACK(4) 45 | optional int32 Int_16 = 16; // 0, hardcoded 46 | } 47 | 48 | optional uint64 FullPhone = 1; // 8618522223333 49 | optional int32 Passive = 3; // 1: XX, 0: IK 50 | optional _struct_5 Struct_5 = 5; 51 | optional string Nick = 7; // XCH_nick 52 | optional fixed32 SessionId = 9; // 53 | optional int32 Int_10 = 10; // 0 force_long_connect, ALI: 1 54 | optional int32 NetworkSubType = 12; // 1:WIFI 111:4g 55 | optional DNS Dns = 15; // Beta only 56 | optional int32 ConnectionSequenceAttempts = 16; // Beta only, 1, starts from 1 57 | optional int32 SignatureMatch = 23; // sha1(getPackageInfo.Signature) == `...` 58 | optional int32 connection_lc = 24; // every login +1 59 | } 60 | -------------------------------------------------------------------------------- /signal/state/record/unacknowledged_prekey.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/util/optional" 6 | ) 7 | 8 | // NewUnackPreKeyMessageItems returns message items that are unacknowledged. 9 | func NewUnackPreKeyMessageItems(preKeyID *optional.Uint32, signedPreKeyID uint32, 10 | baseKey ecc.ECPublicKeyable) *UnackPreKeyMessageItems { 11 | 12 | return &UnackPreKeyMessageItems{ 13 | preKeyID: preKeyID, 14 | signedPreKeyID: signedPreKeyID, 15 | baseKey: baseKey, 16 | } 17 | } 18 | 19 | // NewUnackPreKeyMessageItemsFromStruct will return a new unacknowledged prekey 20 | // message items object from the given structure. 21 | func NewUnackPreKeyMessageItemsFromStruct(structure *UnackPreKeyMessageItemsStructure) *UnackPreKeyMessageItems { 22 | baseKey, _ := ecc.DecodePoint(structure.BaseKey, 0) 23 | return NewUnackPreKeyMessageItems( 24 | structure.PreKeyID, 25 | structure.SignedPreKeyID, 26 | baseKey, 27 | ) 28 | } 29 | 30 | // UnackPreKeyMessageItemsStructure is a serializable structure for unackowledged 31 | // prekey message items. 32 | type UnackPreKeyMessageItemsStructure struct { 33 | PreKeyID *optional.Uint32 34 | SignedPreKeyID uint32 35 | BaseKey []byte 36 | } 37 | 38 | // UnackPreKeyMessageItems is a structure for messages that have not been 39 | // acknowledged. 40 | type UnackPreKeyMessageItems struct { 41 | preKeyID *optional.Uint32 42 | signedPreKeyID uint32 43 | baseKey ecc.ECPublicKeyable 44 | } 45 | 46 | // PreKeyID returns the prekey id of the unacknowledged message. 47 | func (u *UnackPreKeyMessageItems) PreKeyID() *optional.Uint32 { 48 | return u.preKeyID 49 | } 50 | 51 | // SignedPreKeyID returns the signed prekey id of the unacknowledged message. 52 | func (u *UnackPreKeyMessageItems) SignedPreKeyID() uint32 { 53 | return u.signedPreKeyID 54 | } 55 | 56 | // BaseKey returns the ECC public key of the unacknowledged message. 57 | func (u *UnackPreKeyMessageItems) BaseKey() ecc.ECPublicKeyable { 58 | return u.baseKey 59 | } 60 | 61 | // structure will return a serializable base structure 62 | // for unacknowledged prekey message items. 63 | func (u *UnackPreKeyMessageItems) structure() *UnackPreKeyMessageItemsStructure { 64 | return &UnackPreKeyMessageItemsStructure{ 65 | PreKeyID: u.preKeyID, 66 | SignedPreKeyID: u.signedPreKeyID, 67 | BaseKey: u.baseKey.Serialize(), 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /signal/provision/cipher.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "encoding/json" 8 | "fmt" 9 | 10 | "wa/signal/cipher" 11 | "wa/signal/ecc" 12 | "wa/signal/kdf" 13 | "wa/signal/keys/root" 14 | "wa/signal/util/bytehelper" 15 | ) 16 | 17 | type ProvisionMessage struct { 18 | IdentityKeyPublic []byte `json:"identity_key_public"` 19 | IdentityKeyPrivate []byte `json:"identity_key_private"` 20 | UserId string `json:"user_id"` 21 | ProvisioningCode string `json:"provisioning_code"` 22 | ProfileKey []byte `json:"profile_key"` 23 | } 24 | 25 | type ProvisionEnvelope struct { 26 | PublicKey []byte `json:"public_key"` 27 | Body []byte `json:"body"` 28 | } 29 | 30 | func verifyMAC(key, input, mac []byte) bool { 31 | m := hmac.New(sha256.New, key) 32 | m.Write(input) 33 | return hmac.Equal(m.Sum(nil), mac) 34 | } 35 | 36 | func Decrypt(privateKey string, content string) (string, error) { 37 | ourPrivateKey, err := base64.StdEncoding.DecodeString(privateKey) 38 | if err != nil { 39 | return "", err 40 | } 41 | envelopeDecode, err := base64.StdEncoding.DecodeString(content) 42 | if err != nil { 43 | return "", err 44 | } 45 | 46 | var envelope ProvisionEnvelope 47 | if err := json.Unmarshal(envelopeDecode, &envelope); err != nil { 48 | return "", err 49 | } 50 | 51 | publicKeyable, _ := ecc.DecodePoint(envelope.PublicKey, 0) 52 | masterEphemeral := publicKeyable.PublicKey() 53 | message := envelope.Body 54 | if message[0] != 1 { 55 | return "", fmt.Errorf("Bad version number on ProvisioningMessage %s", err.Error()) 56 | } 57 | 58 | iv := message[1 : 16+1] 59 | mac := message[len(message)-32:] 60 | ivAndCiphertext := message[0 : len(message)-32] 61 | cipherText := message[16+1 : len(message)-32] 62 | 63 | sharedSecret := kdf.CalculateSharedSecret(masterEphemeral, bytehelper.SliceToArray(ourPrivateKey)) 64 | derivedSecretBytes, err := kdf.DeriveSecrets(sharedSecret[:], nil, []byte("Mixin Provisioning Message"), root.DerivedSecretsSize) 65 | if err != nil { 66 | return "", err 67 | } 68 | aesKey := derivedSecretBytes[:32] 69 | macKey := derivedSecretBytes[32:] 70 | 71 | if !verifyMAC(macKey, ivAndCiphertext, mac) { 72 | return "", fmt.Errorf("Verify Mac failed") 73 | } 74 | plaintext, err := cipher.DecryptCbc(iv, aesKey, cipherText) 75 | if err != nil { 76 | return "", err 77 | } 78 | return string(plaintext), nil 79 | } 80 | -------------------------------------------------------------------------------- /core/privacy.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ajson" 5 | "wa/xmpp" 6 | ) 7 | 8 | func (a *Acc) get_jabber_iq_privacy() error { 9 | _, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 10 | Tag: `iq`, 11 | Attrs: []*xmpp.KeyValue{ 12 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 13 | {Key: `type`, Value: `get`}, 14 | {Key: `xmlns`, Value: `jabber:iq:privacy`}, 15 | }, 16 | Children: []*xmpp.Node{ 17 | { 18 | Tag: `query`, 19 | Children: []*xmpp.Node{ 20 | { 21 | Tag: `list`, 22 | Attrs: []*xmpp.KeyValue{ 23 | {Key: `name`, Value: `default`}, 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | }) 30 | return e 31 | } 32 | func (a *Acc) get_status_privacy() error { 33 | _, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 34 | Tag: `iq`, 35 | Attrs: []*xmpp.KeyValue{ 36 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 37 | {Key: `to`, Value: `s.whatsapp.net`}, 38 | {Key: `type`, Value: `get`}, 39 | {Key: `xmlns`, Value: `status`}, 40 | }, 41 | Children: []*xmpp.Node{ 42 | { 43 | Tag: `privacy`, 44 | }, 45 | }, 46 | }) 47 | return e 48 | } 49 | func (a *Acc) get_privacy(xmlns string) error { 50 | _, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 51 | Tag: `iq`, 52 | Attrs: []*xmpp.KeyValue{ 53 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 54 | {Key: `to`, Value: `s.whatsapp.net`, Type: 1}, 55 | {Key: `type`, Value: `get`}, 56 | {Key: `xmlns`, Value: xmlns}, 57 | }, 58 | Children: []*xmpp.Node{ 59 | { 60 | Tag: `privacy`, 61 | }, 62 | }, 63 | }) 64 | return e 65 | } 66 | 67 | func (a *Acc) set_status_privacy() (*xmpp.Node, error) { 68 | nr, e := a.Noise.WriteReadXmppNode(&xmpp.Node{ 69 | Tag: `iq`, 70 | Attrs: []*xmpp.KeyValue{ 71 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 72 | {Key: `to`, Value: `s.whatsapp.net`}, 73 | {Key: `type`, Value: `set`}, 74 | {Key: `xmlns`, Value: `status`}, 75 | }, 76 | Children: []*xmpp.Node{ 77 | { 78 | Tag: `privacy`, 79 | Children: []*xmpp.Node{ 80 | { 81 | Tag: `list`, 82 | Attrs: []*xmpp.KeyValue{ 83 | {Key: `type`, Value: `contacts`}, 84 | }, 85 | }, 86 | }, 87 | }, 88 | }, 89 | }) 90 | return nr, e 91 | } 92 | 93 | func (c Core) SetStatusPrivacy(j *ajson.Json) *ajson.Json { 94 | a, e := GetAccFromJson(j) 95 | if e != nil { 96 | return NewErrRet(e) 97 | } 98 | 99 | nr, e := a.set_status_privacy() 100 | if e != nil { 101 | return NewErrRet(e) 102 | } 103 | return NewJsonRet(nr.ToJson()) 104 | } 105 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module wa 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/AllenDang/giu v0.6.3-0.20220810025217-ab05efcbc636 7 | github.com/AllenDang/imgui-go v1.12.1-0.20220322114136-499bbf6a42ad 8 | github.com/CapacitorSet/ja3-server v0.0.0-20181231152103-b9d2a4d79e7c 9 | github.com/beevik/etree v1.1.0 10 | github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72 11 | github.com/fatih/color v1.13.0 12 | github.com/go-co-op/gocron v1.15.0 13 | github.com/golang/protobuf v1.5.2 14 | github.com/mattn/go-colorable v0.1.12 15 | github.com/pkg/errors v0.9.1 16 | github.com/refraction-networking/utls v1.1.0 17 | github.com/useflyent/fhttp v0.0.0-20211004035111-333f430cfbbf 18 | go.mongodb.org/mongo-driver v1.9.1 19 | go.uber.org/multierr v1.8.0 20 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d 21 | golang.org/x/net v0.0.0-20220630215102-69896b714898 22 | google.golang.org/grpc v1.47.0 23 | google.golang.org/protobuf v1.28.0 24 | gorm.io/driver/sqlite v1.3.5 25 | gorm.io/gorm v1.23.7 26 | ) 27 | 28 | require ( 29 | github.com/AllenDang/go-findfont v0.0.0-20200702051237-9f180485aeb8 // indirect 30 | github.com/andybalholm/brotli v1.0.3 // indirect 31 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect 32 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 33 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220516021902-eb3e265c7661 // indirect 34 | github.com/go-stack/stack v1.8.0 // indirect 35 | github.com/golang/snappy v0.0.1 // indirect 36 | github.com/jinzhu/inflection v1.0.0 // indirect 37 | github.com/jinzhu/now v1.1.5 // indirect 38 | github.com/klauspost/compress v1.13.6 // indirect 39 | github.com/mattn/go-isatty v0.0.14 // indirect 40 | github.com/mattn/go-sqlite3 v1.14.12 // indirect 41 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect 42 | github.com/robfig/cron/v3 v3.0.1 // indirect 43 | github.com/sahilm/fuzzy v0.1.0 // indirect 44 | github.com/smartystreets/goconvey v1.7.2 // indirect 45 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 46 | github.com/xdg-go/scram v1.0.2 // indirect 47 | github.com/xdg-go/stringprep v1.0.2 // indirect 48 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 49 | go.uber.org/atomic v1.7.0 // indirect 50 | golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect 51 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 52 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 55 | gopkg.in/eapache/queue.v1 v1.1.0 // indirect 56 | ) 57 | -------------------------------------------------------------------------------- /wam/buffer.go: -------------------------------------------------------------------------------- 1 | package wam 2 | 3 | import ( 4 | "errors" 5 | 6 | "wa/crypto" 7 | ) 8 | 9 | type ByteBuffer struct { 10 | Data []byte 11 | offset int // offset of Data, used for parsing 12 | } 13 | 14 | func (bf *ByteBuffer) read_n(num int) ([]byte, error) { 15 | if len(bf.Data[bf.offset:]) < num { 16 | return nil, errors.New(`not enough bytes`) 17 | } 18 | ret := bf.Data[bf.offset : bf.offset+num] 19 | bf.offset += num 20 | return ret, nil 21 | } 22 | func (bf *ByteBuffer) read_u8() (uint8, error) { 23 | bs, e := bf.read_n(1) 24 | if e != nil { 25 | return 0, e 26 | } 27 | return crypto.LE2U8(bs), nil 28 | } 29 | func (bf *ByteBuffer) read_u16() (uint16, error) { 30 | bs, e := bf.read_n(2) 31 | if e != nil { 32 | return 0, e 33 | } 34 | return crypto.LE2U16(bs), nil 35 | } 36 | func (bf *ByteBuffer) read_u32() (uint32, error) { 37 | bs, e := bf.read_n(4) 38 | if e != nil { 39 | return 0, e 40 | } 41 | return crypto.LE2U32(bs), nil 42 | } 43 | func (bf *ByteBuffer) read_u64() (uint64, error) { 44 | bs, e := bf.read_n(8) 45 | if e != nil { 46 | return 0, e 47 | } 48 | return crypto.LE2U64(bs), nil 49 | } 50 | func (bf *ByteBuffer) put_byte(val uint8) { 51 | bf.Data = append(bf.Data, val) 52 | } 53 | func (bf *ByteBuffer) put_bytes(val []byte) { 54 | bf.Data = append(bf.Data, val...) 55 | } 56 | 57 | func (bf *ByteBuffer) put_number(val int) uint8 { 58 | if val == 0 { // bool 59 | return 1 60 | } 61 | if val == 1 { // bool 62 | return 2 63 | } 64 | if -0x80 <= val && val <= 0x7F { 65 | bf.Data = append(bf.Data, crypto.U82LE(uint8(val))...) 66 | return 3 67 | } 68 | if -0x8000 <= val && val <= 0x7FFF { 69 | bf.Data = append(bf.Data, crypto.U162LE(uint16(val))...) 70 | return 4 71 | } 72 | if -0x80000000 <= val && val <= 0x7FFFFFFF { 73 | bf.Data = append(bf.Data, crypto.U322LE(uint32(val))...) 74 | return 5 75 | } 76 | 77 | bf.Data = append(bf.Data, crypto.U642LE(uint64(val))...) 78 | return 6 79 | } 80 | 81 | func (bf *ByteBuffer) put_u32(val uint32) int { 82 | if val <= 0xff { 83 | bf.Data = append(bf.Data, crypto.U82LE(uint8(val))...) 84 | return 1 85 | } 86 | if val <= 0xffff { 87 | bf.Data = append(bf.Data, crypto.U162LE(uint16(val))...) 88 | return 2 89 | } 90 | 91 | bf.Data = append(bf.Data, crypto.U322LE(uint32(val))...) 92 | return 4 93 | } 94 | 95 | func (bf *ByteBuffer) read_n_bytes_as_int32( 96 | numBytes int, 97 | ) (int32, error) { 98 | var ret int32 = 0 99 | for i := 0; i < numBytes; i++ { 100 | b, e := bf.read_u8() 101 | if e != nil { 102 | return 0, e 103 | } 104 | ret |= (int32(b)) << (i << 3) 105 | } 106 | return ret, nil 107 | } 108 | -------------------------------------------------------------------------------- /core/session.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | 6 | "wa/signal/ecc" 7 | "wa/signal/keys/identity" 8 | "wa/signal/keys/prekey" 9 | "wa/signal/protocol" 10 | "wa/signal/session" 11 | "wa/signal/util/bytehelper" 12 | "wa/signal/util/optional" 13 | 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | /* 18 | This function doesn't expand multi-device, 19 | param `jid_arr` should be already expanded. 20 | */ 21 | func (a *Acc) ensure_session_builder( 22 | jid_arr []string, 23 | ) (map[string]*session.Builder, error) { 24 | 25 | ret := map[string]*session.Builder{} 26 | 27 | no_session_jids := []string{} 28 | 29 | for _, jid := range jid_arr { 30 | recid, devid, e := split_jid(jid) 31 | if e != nil { 32 | return nil, e 33 | } 34 | 35 | peer_addr := protocol.NewSignalAddress(fmt.Sprintf("%d", recid), devid) 36 | 37 | if a.Store.ContainsSession(peer_addr) { 38 | sb := session.NewBuilder( 39 | a.Store, a.Store, a.Store, a.Store, peer_addr) 40 | ret[jid] = sb 41 | 42 | continue 43 | } 44 | // if no previous session 45 | no_session_jids = append(no_session_jids, jid) 46 | } 47 | 48 | // if all session presents 49 | if len(no_session_jids) == 0 { 50 | return ret, nil 51 | } 52 | 53 | // 1. get_encrypt 54 | emap, e := a.get_encrypt(no_session_jids, `key`) 55 | if e != nil { 56 | return nil, errors.Wrap(e, "get_encrypt Fail") 57 | } 58 | 59 | for _, jid := range no_session_jids { 60 | user, ok := emap[jid] 61 | if !ok { 62 | return nil, errors.Wrap(e, `WTF, missing Encrypt Result for jid `+jid) 63 | } 64 | 65 | recid, devid, _ := split_jid(jid) 66 | 67 | // if bot account has no prekey left 68 | var prekey_ ecc.ECPublicKeyable = nil 69 | var prekey_id *optional.Uint32 = optional.NewEmptyUint32() 70 | if user.Prekey != nil { 71 | prekey_ = ecc.NewDjbECPublicKey(user.Prekey) 72 | prekey_id = optional.NewOptionalUint32(uint32(user.PrekeyId)) 73 | } else { 74 | a.Log.Debug("prekey is nil") 75 | } 76 | 77 | // 2. create session 78 | pkb := prekey.NewBundle( 79 | user.RegId, 80 | devid, 81 | prekey_id, 82 | user.SpkId, 83 | prekey_, 84 | ecc.NewDjbECPublicKey(user.Spk), 85 | bytehelper.SliceToArray64(user.SpkSig), 86 | identity.NewKeyFromBytes(bytehelper.SliceToArray(user.Identity)), 87 | ) 88 | 89 | peer_addr := protocol.NewSignalAddress(fmt.Sprintf("%d", recid), devid) 90 | sb := session.NewBuilder( 91 | a.Store, a.Store, a.Store, a.Store, peer_addr) 92 | if e := sb.ProcessBundle(pkb); e != nil { 93 | return nil, errors.Wrap(e, `fail sb.ProcessBundle `) 94 | } 95 | ret[jid] = sb 96 | } 97 | 98 | return ret, nil 99 | } 100 | -------------------------------------------------------------------------------- /signal/keys/prekey/bundle.go: -------------------------------------------------------------------------------- 1 | // Package prekey provides prekey bundle structures for calculating 2 | // a new Signal session with a user asyncronously. 3 | package prekey 4 | 5 | import ( 6 | "wa/signal/ecc" 7 | "wa/signal/keys/identity" 8 | "wa/signal/util/optional" 9 | ) 10 | 11 | // NewBundle returns a Bundle structure that contains a remote PreKey 12 | // and collection of associated items. 13 | func NewBundle( 14 | registrationID, 15 | deviceID uint32, 16 | preKeyID *optional.Uint32, 17 | signedPreKeyID uint32, 18 | preKeyPublic, 19 | signedPreKeyPublic ecc.ECPublicKeyable, 20 | signedPreKeySig [64]byte, 21 | identityKey *identity.Key, 22 | ) *Bundle { 23 | 24 | bundle := Bundle{ 25 | registrationID: registrationID, 26 | deviceID: deviceID, 27 | preKeyID: preKeyID, 28 | preKeyPublic: preKeyPublic, 29 | signedPreKeyID: signedPreKeyID, 30 | signedPreKeyPublic: signedPreKeyPublic, 31 | signedPreKeySignature: signedPreKeySig, 32 | identityKey: identityKey, 33 | } 34 | 35 | return &bundle 36 | } 37 | 38 | // Bundle is a structure that contains a remote PreKey and collection 39 | // of associated items. 40 | type Bundle struct { 41 | registrationID uint32 42 | deviceID uint32 43 | preKeyID *optional.Uint32 44 | preKeyPublic ecc.ECPublicKeyable 45 | signedPreKeyID uint32 46 | signedPreKeyPublic ecc.ECPublicKeyable 47 | signedPreKeySignature [64]byte 48 | identityKey *identity.Key 49 | } 50 | 51 | // DeviceID returns the device ID this PreKey belongs to. 52 | func (b *Bundle) DeviceID() uint32 { 53 | return b.deviceID 54 | } 55 | 56 | // PreKeyID returns the unique key ID for this PreKey. 57 | func (b *Bundle) PreKeyID() *optional.Uint32 { 58 | return b.preKeyID 59 | } 60 | 61 | // PreKey returns the public key for this PreKey. 62 | func (b *Bundle) PreKey() ecc.ECPublicKeyable { 63 | return b.preKeyPublic 64 | } 65 | 66 | // SignedPreKeyID returns the unique key ID for this 67 | // signed PreKey. 68 | func (b *Bundle) SignedPreKeyID() uint32 { 69 | return b.signedPreKeyID 70 | } 71 | 72 | // SignedPreKey returns the signed PreKey for this 73 | // PreKeyBundle. 74 | func (b *Bundle) SignedPreKey() ecc.ECPublicKeyable { 75 | return b.signedPreKeyPublic 76 | } 77 | 78 | // SignedPreKeySignature returns the signature over the 79 | // signed PreKey. 80 | func (b *Bundle) SignedPreKeySignature() [64]byte { 81 | return b.signedPreKeySignature 82 | } 83 | 84 | // IdentityKey returns the Identity Key of this PreKey's owner. 85 | func (b *Bundle) IdentityKey() *identity.Key { 86 | return b.identityKey 87 | } 88 | 89 | // RegistrationID returns the registration ID associated with 90 | // this PreKey. 91 | func (b *Bundle) RegistrationID() uint32 { 92 | return b.registrationID 93 | } 94 | -------------------------------------------------------------------------------- /signal/keys/message/key.go: -------------------------------------------------------------------------------- 1 | // Package message provides a structure for message keys, which are symmetric 2 | // keys used for the encryption/decryption of Signal messages. 3 | package message 4 | 5 | import ( 6 | "wa/signal/pb" 7 | 8 | "google.golang.org/protobuf/proto" 9 | ) 10 | 11 | // DerivedSecretsSize is the size of the derived secrets for message keys. 12 | const DerivedSecretsSize = 80 13 | 14 | // CipherKeyLength is the length of the actual cipher key used for messages. 15 | const CipherKeyLength = 32 16 | 17 | // MacKeyLength is the length of the message authentication code in bytes. 18 | const MacKeyLength = 32 19 | 20 | // IVLength is the length of the initialization vector in bytes. 21 | const IVLength = 16 22 | 23 | // KdfSalt is used as the Salt for message keys to derive secrets using a Key Derivation Function 24 | const KdfSalt string = "WhisperMessageKeys" 25 | 26 | // NewKeys returns a new message keys structure with the given cipherKey, mac, iv, and index. 27 | func NewKeys(cipherKey, macKey, iv []byte, index uint32) *Keys { 28 | messageKeys := Keys{ 29 | cipherKey: cipherKey, 30 | macKey: macKey, 31 | iv: iv, 32 | index: index, 33 | } 34 | 35 | return &messageKeys 36 | } 37 | 38 | // NewKeysFromStruct will return a new message keys object from the 39 | // given serializeable structure. 40 | func NewKeysFromStruct(structure *pb.SessionStructure_Chain_MessageKey) *Keys { 41 | return NewKeys( 42 | structure.CipherKey, 43 | structure.MacKey, 44 | structure.Iv, 45 | structure.GetIndex(), 46 | ) 47 | } 48 | 49 | // NewStructFromKeys returns a serializeable structure of message keys. 50 | func NewStructFromKeys(keys *Keys) *pb.SessionStructure_Chain_MessageKey { 51 | return &pb.SessionStructure_Chain_MessageKey{ 52 | CipherKey: keys.cipherKey, 53 | MacKey: keys.macKey, 54 | Iv: keys.iv, 55 | Index: proto.Uint32(keys.index), 56 | } 57 | } 58 | 59 | // Keys is a structure to hold all the keys for a single MessageKey, including the 60 | // cipherKey, mac, iv, and index of the chain key. MessageKeys are used to 61 | // encrypt individual messages. 62 | type Keys struct { 63 | cipherKey []byte 64 | macKey []byte 65 | iv []byte 66 | index uint32 67 | } 68 | 69 | // CipherKey is the key used to produce ciphertext. 70 | func (k *Keys) CipherKey() []byte { 71 | return k.cipherKey 72 | } 73 | 74 | // MacKey returns the message's message authentication code. 75 | func (k *Keys) MacKey() []byte { 76 | return k.macKey 77 | } 78 | 79 | // Iv returns the message keys' initialization vector. The IV is a fixed-size input 80 | // to a cryptographic primitive. 81 | func (k *Keys) Iv() []byte { 82 | return k.iv 83 | } 84 | 85 | // Index returns the number of times the chain key has been put through a key derivation 86 | // function to generate this message key. 87 | func (k *Keys) Index() uint32 { 88 | return k.index 89 | } 90 | -------------------------------------------------------------------------------- /signal/state/record/signed_prekey.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/pb" 6 | "wa/signal/util/bytehelper" 7 | 8 | "github.com/golang/protobuf/proto" 9 | ) 10 | 11 | func NewSignedPreKeyFromBytes(serialized []byte) (*SignedPreKey, error) { 12 | p := &pb.SignedPreKeyRecordStructure{} 13 | e := proto.Unmarshal(serialized, p) 14 | if e != nil { 15 | return nil, e 16 | } 17 | return NewSignedPreKeyFromStruct(p) 18 | } 19 | 20 | // NewSignedPreKeyFromStruct returns a SignedPreKey record using the given 21 | // serializable structure. 22 | func NewSignedPreKeyFromStruct(structure *pb.SignedPreKeyRecordStructure) (*SignedPreKey, error) { 23 | 24 | // Create the signed prekey record from the structure. 25 | signedPreKey := &SignedPreKey{ 26 | structure: structure, 27 | signature: bytehelper.SliceToArray64(structure.Signature), 28 | } 29 | 30 | // Generate the ECC key from bytes. 31 | publicKey := ecc.NewDjbECPublicKey(structure.PublicKey) 32 | privateKey := ecc.NewDjbECPrivateKey(bytehelper.SliceToArray(structure.PrivateKey)) 33 | keyPair := ecc.NewECKeyPair(publicKey, privateKey) 34 | signedPreKey.keyPair = keyPair 35 | 36 | return signedPreKey, nil 37 | } 38 | 39 | // NewSignedPreKey record creates a new signed pre key record 40 | // with the given properties. 41 | func NewSignedPreKey( 42 | id uint32, 43 | timestamp int64, 44 | keyPair *ecc.ECKeyPair, 45 | sig [64]byte, 46 | ) *SignedPreKey { 47 | 48 | return &SignedPreKey{ 49 | structure: &pb.SignedPreKeyRecordStructure{ 50 | Id: proto.Uint32(id), 51 | Timestamp: proto.Uint64(uint64(timestamp)), 52 | PublicKey: keyPair.PublicKey().Serialize(), 53 | PrivateKey: bytehelper.ArrayToSlice(keyPair.PrivateKey().Serialize()), 54 | Signature: bytehelper.ArrayToSlice64(sig), 55 | }, 56 | keyPair: keyPair, 57 | signature: sig, 58 | } 59 | } 60 | 61 | // SignedPreKey record is a structure for storing a signed 62 | // pre key in a SignedPreKey store. 63 | type SignedPreKey struct { 64 | structure *pb.SignedPreKeyRecordStructure 65 | keyPair *ecc.ECKeyPair 66 | signature [64]byte 67 | } 68 | 69 | // ID returns the record's id. 70 | func (s *SignedPreKey) ID() uint32 { 71 | return s.structure.GetId() 72 | } 73 | 74 | // Timestamp returns the record's timestamp 75 | func (s *SignedPreKey) Timestamp() int64 { 76 | return int64(s.structure.GetTimestamp()) 77 | } 78 | 79 | // KeyPair returns the signed pre key record's key pair. 80 | func (s *SignedPreKey) KeyPair() *ecc.ECKeyPair { 81 | return s.keyPair 82 | } 83 | 84 | // Signature returns the record's signed prekey signature. 85 | func (s *SignedPreKey) Signature() [64]byte { 86 | return s.signature 87 | } 88 | 89 | // Serialize uses the SignedPreKey serializer to return the SignedPreKey 90 | // as serialized bytes. 91 | func (s *SignedPreKey) Serialize() []byte { 92 | bs, e := proto.Marshal(s.structure) 93 | if e != nil { 94 | return nil 95 | } 96 | return bs 97 | } 98 | -------------------------------------------------------------------------------- /signal/util/bytehelper/byte.go: -------------------------------------------------------------------------------- 1 | package bytehelper 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // SliceToArray will convert byte slice to a 32 byte array 8 | func SliceToArray(bytes []byte) [32]byte { 9 | var byteArray [32]byte 10 | copy(byteArray[:], bytes) 11 | return byteArray 12 | } 13 | 14 | // SliceToArray64 will convert byte slice to a 64 byte array 15 | func SliceToArray64(bytes []byte) [64]byte { 16 | var byteArray [64]byte 17 | copy(byteArray[:], bytes) 18 | return byteArray 19 | } 20 | 21 | // ArrayToSlice will convert a 32 byte array to byte slice 22 | func ArrayToSlice(bytes [32]byte) []byte { 23 | return bytes[:] 24 | } 25 | 26 | // ArrayToSlice64 will convert a 64 byte array to byte slice 27 | func ArrayToSlice64(bytes [64]byte) []byte { 28 | return bytes[:] 29 | } 30 | 31 | // Split will take the given byte array and split it into half, 32 | // with the first half being "firstLength" in size and the second 33 | // half "secondLength" in size. 34 | func Split(input []byte, firstLength, secondLength int) [][]byte { 35 | parts := make([][]byte, 2) 36 | 37 | parts[0] = make([]byte, firstLength) 38 | copy(parts[0], input[:firstLength]) 39 | 40 | parts[1] = make([]byte, secondLength) 41 | copy(parts[1], input[firstLength:]) 42 | 43 | return parts 44 | } 45 | 46 | // SplitThree will take the given byte array and split it into thirds, 47 | // with the first third being "firstLength" in size, the second third 48 | // being "secondLength" in size, and the last third being "thirdLength" 49 | // in size. 50 | func SplitThree(input []byte, firstLength, secondLength, thirdLength int) ([][]byte, error) { 51 | if input == nil || firstLength < 0 || secondLength < 0 || thirdLength < 0 || 52 | len(input) < firstLength+secondLength+thirdLength { 53 | 54 | return nil, errors.New("Input too small: " + string(input)) 55 | } 56 | 57 | parts := make([][]byte, 3) 58 | 59 | parts[0] = make([]byte, firstLength) 60 | copy(parts[0], input[:firstLength]) 61 | 62 | parts[1] = make([]byte, secondLength) 63 | copy(parts[1], input[firstLength:][:secondLength]) 64 | 65 | parts[2] = make([]byte, thirdLength) 66 | copy(parts[2], input[firstLength+secondLength:]) 67 | 68 | return parts, nil 69 | } 70 | 71 | // Trim will trim the given byte array to the given length. 72 | func Trim(input []byte, length int) []byte { 73 | result := make([]byte, length) 74 | copy(result, input[:length]) 75 | 76 | return result 77 | } 78 | 79 | // Bytes5ToInt64 will convert the given byte array and offset to an int64. 80 | func Bytes5ToInt64(bytes []byte, offset int) int64 { 81 | 82 | value := (int64(bytes[offset]&0xff) << 32) | 83 | (int64(bytes[offset+1]&0xff) << 24) | 84 | (int64(bytes[offset+2]&0xff) << 16) | 85 | (int64(bytes[offset+3]&0xff) << 8) | 86 | int64(bytes[offset+4]&0xff) 87 | 88 | return value 89 | } 90 | 91 | // CopySlice returns a copy of the given bytes. 92 | func CopySlice(bytes []byte) []byte { 93 | cp := make([]byte, len(bytes)) 94 | copy(cp, bytes) 95 | 96 | return cp 97 | } 98 | -------------------------------------------------------------------------------- /net/http.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "fmt" 8 | "io/ioutil" 9 | "strings" 10 | "time" 11 | 12 | fhttp "github.com/useflyent/fhttp" 13 | 14 | utls "github.com/refraction-networking/utls" 15 | 16 | "wa/def" 17 | ) 18 | 19 | func UA(is_biz bool, ver, android_ver, brand, model string) string { 20 | smba := "Android" 21 | if is_biz { 22 | smba = "SMBA" 23 | } 24 | ua := fmt.Sprintf( 25 | "WhatsApp/%s %s/%s Device/%s-%s", 26 | ver, 27 | smba, 28 | android_ver, 29 | brand, 30 | strings.ReplaceAll(model, " ", "_"), 31 | ) 32 | 33 | return ua 34 | } 35 | 36 | func HttpReq( 37 | method, host, url_, 38 | proxyAddr string, dns map[string]string, ja3_str string, 39 | hdr fhttp.Header, 40 | post_body []byte, 41 | ) (int, []byte, error) { 42 | 43 | if ja3_str == `` { 44 | ja3_str = def.Ja3_WhiteMi6x 45 | } 46 | var ja3_transport *fhttp.Transport 47 | var e error 48 | 49 | if len(proxyAddr) > 0 { 50 | ja3_transport, e = NewTransportWithConfigAndSocks5( 51 | ja3_str, proxyAddr, &utls.Config{ 52 | InsecureSkipVerify: true, 53 | MaxVersion: tls.VersionTLS13, 54 | }) 55 | } else { 56 | ja3_transport, e = NewTransportWithConfig( 57 | ja3_str, &utls.Config{ 58 | InsecureSkipVerify: true, 59 | MaxVersion: tls.VersionTLS13, 60 | }) 61 | } 62 | if e != nil { 63 | return 0, nil, e 64 | } 65 | 66 | client := &fhttp.Client{ 67 | Transport: ja3_transport, 68 | } 69 | 70 | // timeout 71 | ctx, cancel := context.WithCancel(context.TODO()) 72 | timer := time.AfterFunc(time.Duration(def.NET_TIMEOUT)*time.Second, cancel) 73 | defer timer.Stop() 74 | 75 | host_modified := host 76 | 77 | if dns != nil { 78 | ip, ok := dns[host] 79 | if ok && len(ip) > 0 { 80 | host_modified = ip 81 | } 82 | } 83 | 84 | // for debugging 85 | //req, e := fhttp.NewRequest(method, "http://"+host_modified+url_, bytes.NewReader(post_body)) 86 | req, e := fhttp.NewRequest(method, "https://"+host_modified+url_, bytes.NewReader(post_body)) 87 | if e != nil { 88 | return 0, nil, e 89 | } 90 | req = req.WithContext(ctx) 91 | 92 | // force http header.Host, if changed by dns 93 | //if host != host_modified { 94 | //req.Host = host 95 | //} 96 | 97 | req.Header = hdr 98 | 99 | resp, e := client.Do(req) 100 | if e != nil { 101 | return 0, nil, e 102 | } 103 | 104 | defer resp.Body.Close() 105 | body, e := ioutil.ReadAll(resp.Body) 106 | if e != nil { 107 | return 0, nil, e 108 | } 109 | 110 | return resp.StatusCode, body, nil 111 | } 112 | 113 | func HttpGet(host, url_, proxyAddr string, dns map[string]string, ja3_str string, hdr fhttp.Header) (int, []byte, error) { 114 | return HttpReq(`GET`, host, url_, proxyAddr, dns, ja3_str, hdr, nil) 115 | } 116 | func HttpPost(host, url_, proxyAddr string, dns map[string]string, ja3_str string, hdr fhttp.Header, body []byte) (int, []byte, error) { 117 | return HttpReq(`POST`, host, url_, proxyAddr, dns, ja3_str, hdr, body) 118 | } 119 | -------------------------------------------------------------------------------- /signal/groups/session_builder.go: -------------------------------------------------------------------------------- 1 | // Package groups is responsible for setting up group SenderKey encrypted sessions. 2 | // Once a session has been established, GroupCipher can be used to encrypt/decrypt 3 | // messages in that session. 4 | // 5 | // The built sessions are unidirectional: they can be used either for sending or 6 | // for receiving, but not both. Sessions are constructed per (groupId + senderId + 7 | // deviceId) tuple. Remote logical users are identified by their senderId, and each 8 | // logical recipientId can have multiple physical devices. 9 | package groups 10 | 11 | import ( 12 | "wa/signal/groups/state/record" 13 | "wa/signal/groups/state/store" 14 | "wa/signal/protocol" 15 | "wa/signal/util/keyhelper" 16 | ) 17 | 18 | // NewGroupSessionBuilder will return a new group session builder. 19 | func NewGroupSessionBuilder( 20 | senderKeyStore store.SenderKey, 21 | ) *SessionBuilder { 22 | 23 | return &SessionBuilder{ 24 | senderKeyStore: senderKeyStore, 25 | } 26 | } 27 | 28 | // SessionBuilder is a structure for building group sessions. 29 | type SessionBuilder struct { 30 | senderKeyStore store.SenderKey 31 | } 32 | 33 | // Process will process an incoming group message and set up the corresponding 34 | // session for it. 35 | func (b *SessionBuilder) Process(senderKeyName *protocol.SenderKeyName, 36 | msg *protocol.SenderKeyDistributionMessage) error { 37 | 38 | senderKeyRecord, e := b.senderKeyStore.LoadSenderKey(senderKeyName) 39 | if e != nil { 40 | return e 41 | } 42 | if senderKeyRecord == nil { 43 | senderKeyRecord = record.NewSenderKey() 44 | } 45 | senderKeyRecord.AddSenderKeyState(msg.ID(), msg.Iteration(), msg.ChainKey(), msg.SignatureKey()) 46 | return b.senderKeyStore.StoreSenderKey(senderKeyName, senderKeyRecord) 47 | } 48 | 49 | // Create will create a new group session for the given name. 50 | func (b *SessionBuilder) Create(senderKeyName *protocol.SenderKeyName) (*protocol.SenderKeyDistributionMessage, error) { 51 | // Load the senderkey by name 52 | senderKeyRecord, e := b.senderKeyStore.LoadSenderKey(senderKeyName) 53 | if e != nil { 54 | return nil, e 55 | } 56 | 57 | // If the record is empty, generate new keys. 58 | if senderKeyRecord == nil || senderKeyRecord.IsEmpty() { 59 | senderKeyRecord = record.NewSenderKey() 60 | signingKey, err := keyhelper.GenerateSenderSigningKey() 61 | if err != nil { 62 | return nil, err 63 | } 64 | senderKeyRecord.SetSenderKeyState( 65 | keyhelper.GenerateSenderKeyID(), 0, 66 | keyhelper.GenerateSenderKey(), 67 | signingKey, 68 | ) 69 | b.senderKeyStore.StoreSenderKey(senderKeyName, senderKeyRecord) 70 | } 71 | 72 | // Get the senderkey state. 73 | state, err := senderKeyRecord.SenderKeyState() 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | // Create the group message to return. 79 | senderKeyDistributionMessage := protocol.NewSenderKeyDistributionMessage( 80 | state.KeyID(), 81 | state.SenderChainKey().Iteration(), 82 | state.SenderChainKey().Seed(), 83 | state.SigningKey().PublicKey(), 84 | ) 85 | 86 | return senderKeyDistributionMessage, nil 87 | } 88 | -------------------------------------------------------------------------------- /core/safetynet.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ahex" 5 | "wa/xmpp" 6 | ) 7 | 8 | /* 9 | { 10 | "Attrs": { 11 | "from": "s.whatsapp.net" 12 | }, 13 | "Children": [ 14 | { 15 | "Children": [ 16 | { 17 | "Attrs": { 18 | "key": "AIzASyDR5yfaG7OG8sMTUj8kfQEb8T9pN8BM6Lk", 19 | "nonce": "ATcpqLIi05UFKrmxuYoopmiQsp5Y4wywTkLIPn0TgUZ0uDvVDa34VByseb9-kStEquAKtg9xbHlrOiZD3zkQ5XQb" 20 | }, 21 | "Tag": "attestation" 22 | }, 23 | { 24 | "Attrs": { 25 | "count": "10" 26 | }, 27 | "Tag": "verify_apps" 28 | } 29 | ], 30 | "Tag": "safetynet" 31 | } 32 | ], 33 | "Tag": "ib" 34 | } 35 | */ 36 | 37 | func New_Hook_Safetynet(a *Acc) func(...any) error { 38 | return func(args ...any) error { 39 | n := args[0].(*xmpp.Node) 40 | 41 | if len(n.Children) == 0 { 42 | return nil 43 | } 44 | ch0 := n.Children[0] 45 | if ch0.Tag != `safetynet` { 46 | return nil 47 | } 48 | 49 | for _, ch := range ch0.Children { 50 | switch ch.Tag { 51 | case `attestation`: 52 | a.Store.SetSafetynetAttestation() 53 | case `verify_apps`: 54 | a.Store.SetSafetynetVerifyApps() 55 | } 56 | } 57 | return nil 58 | } 59 | } 60 | 61 | func (a *Acc) attestation(has_google_play bool) error { 62 | var code string 63 | var data []byte 64 | 65 | if has_google_play { 66 | code = `7` 67 | data = ahex.Dec(`373a20`) // "7: " 68 | } else { 69 | code = `1001` 70 | // my 71 | //data = ahex.Dec(`4174746573746174696f6e2041504920556e617661696c61626c652e20436f6e6e656374696f6e20726573756c7420636f64653a2039`) 72 | // emu 73 | data = ahex.Dec(`476f6f676c6520506c617920536572766963657320556e617661696c61626c652e20436f6e6e656374696f6e20726573756c7420636f64653a2039`) 74 | } 75 | 76 | e := a.Noise.WriteXmppNode(&xmpp.Node{ 77 | Tag: `ib`, 78 | Children: []*xmpp.Node{ 79 | { 80 | Tag: `attestation`, 81 | Children: []*xmpp.Node{ 82 | { 83 | Tag: `error`, 84 | Attrs: []*xmpp.KeyValue{ 85 | {Key: `code`, Value: code}, 86 | }, 87 | Data: data, 88 | }, 89 | }, 90 | }, 91 | }, 92 | }) 93 | return e 94 | } 95 | func (a *Acc) verify_apps(has_google_play bool) error { 96 | var ch *xmpp.Node 97 | if has_google_play { 98 | ch = &xmpp.Node{ 99 | Tag: `apps`, 100 | Attrs: []*xmpp.KeyValue{ 101 | {Key: `actual_count`, Value: `0`}, 102 | }, 103 | } 104 | } else { 105 | ch = &xmpp.Node{ 106 | Tag: `error`, 107 | Attrs: []*xmpp.KeyValue{ 108 | {Key: `code`, Value: `1001`}, 109 | }, 110 | Data: ahex.Dec(`476f6f676c6520506c617920536572766963657320556e617661696c61626c652e20436f6e6e656374696f6e20726573756c7420636f64653a2039`), 111 | } 112 | } 113 | return a.Noise.WriteXmppNode(&xmpp.Node{ 114 | Tag: `ib`, 115 | Children: []*xmpp.Node{ 116 | { 117 | Tag: `verify_apps`, 118 | Children: []*xmpp.Node{ 119 | ch, 120 | }, 121 | }, 122 | }, 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /signal/ecc/curve.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | 8 | "golang.org/x/crypto/curve25519" 9 | 10 | "ahex" 11 | "algo/xed25519" 12 | "wa/signal/util/bytehelper" 13 | ) 14 | 15 | // DjbType is the Diffie-Hellman curve type (curve25519) created by D. J. Bernstein. 16 | const DjbType = 0x05 17 | 18 | // DecodePoint will take the given bytes and offset and return an ECPublicKeyable object. 19 | // This is used to check the byte at the given offset in the byte array for a special 20 | // "type" byte that will determine the key type. Currently only DJB EC keys are supported. 21 | func DecodePoint(bytes []byte, offset int) (ECPublicKeyable, error) { 22 | ret := NewDjbECPublicKey(bytes[offset:]) 23 | if ret == nil { 24 | return nil, errors.New("Fail Decode Point: " + ahex.Enc(bytes)) 25 | } 26 | return ret, nil 27 | } 28 | 29 | func CreateKeyPair(privateKey []byte) *ECKeyPair { 30 | var private, public [32]byte 31 | copy(private[:], privateKey) 32 | 33 | private[0] &= 248 34 | private[31] &= 127 35 | private[31] |= 64 36 | 37 | curve25519.ScalarBaseMult(&public, &private) 38 | 39 | // Put data into our keypair struct 40 | djbECPub := NewDjbECPublicKey(bytehelper.ArrayToSlice(public)) 41 | djbECPriv := NewDjbECPrivateKey(private) 42 | keypair := NewECKeyPair(djbECPub, djbECPriv) 43 | 44 | return keypair 45 | } 46 | 47 | // GenerateKeyPair returns an EC Key Pair. 48 | func GenerateKeyPair() (*ECKeyPair, error) { 49 | // Get cryptographically secure random numbers. 50 | random := rand.Reader 51 | 52 | // Create a byte array for our public and private keys. 53 | var private, public [32]byte 54 | 55 | // Generate some random data 56 | _, err := io.ReadFull(random, private[:]) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // Documented at: http://cr.yp.to/ecdh.html 62 | private[0] &= 248 63 | private[31] &= 127 64 | private[31] |= 64 65 | 66 | curve25519.ScalarBaseMult(&public, &private) 67 | 68 | // Put data into our keypair struct 69 | djbECPub := NewDjbECPublicKey(bytehelper.ArrayToSlice(public)) 70 | djbECPriv := NewDjbECPrivateKey(private) 71 | keypair := NewECKeyPair(djbECPub, djbECPriv) 72 | 73 | return keypair, nil 74 | } 75 | 76 | func VerifySignature(signingKey ECPublicKeyable, message []byte, signature [64]byte) bool { 77 | publicKey := signingKey.PublicKey() 78 | valid := verify(publicKey, message, &signature) 79 | return valid 80 | } 81 | 82 | //var rands = []string{} 83 | 84 | //func SetRands(r []string) { 85 | //rands = r 86 | //} 87 | 88 | func CalculateSignature(signingKey ECPrivateKeyable, message []byte) ([64]byte, error) { 89 | privateKey := signingKey.Serialize() 90 | 91 | // for test 92 | //var random []byte 93 | //if len(rands) > 0 { 94 | //random = ahex.Dec(rands[0]) 95 | //rands = rands[1:] 96 | //} else { 97 | //random = arand.Bytes(64) 98 | //} 99 | //sig, e := xed25519.DoSign(privateKey[:], message, random) 100 | 101 | sig, e := xed25519.Sign(privateKey[:], message) 102 | if e != nil { 103 | return [64]byte{}, e 104 | } 105 | return bytehelper.SliceToArray64(sig), nil 106 | } 107 | -------------------------------------------------------------------------------- /signal/util/keyhelper/key.go: -------------------------------------------------------------------------------- 1 | // Package keyhelper is based on: https://github.com/WhisperSystems/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/util/KeyHelper.java 2 | package keyhelper 3 | 4 | import ( 5 | "crypto/rand" 6 | "encoding/binary" 7 | "time" 8 | 9 | "wa/signal/ecc" 10 | "wa/signal/keys/identity" 11 | "wa/signal/state/record" 12 | ) 13 | 14 | // GenerateIdentityKeyPair generates an identity keypair used for 15 | // signing. Clients should only do this once at install time. 16 | func GenerateIdentityKeyPair() (*identity.KeyPair, error) { 17 | keyPair, err := ecc.GenerateKeyPair() 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | publicKey := identity.NewKey(keyPair.PublicKey()) 23 | return identity.NewKeyPair(publicKey, keyPair.PrivateKey()), nil 24 | } 25 | 26 | // GeneratePreKeys generates a list of PreKeys. Client shsould do this at 27 | // install time, and subsequently any time the list of PreKeys stored on 28 | // the server runs low. 29 | // 30 | // PreKeys IDs are shorts, so they will eventually be repeated. Clients 31 | // should store PreKeys in a circular buffer, so that they are repeated 32 | // as infrequently as possible. 33 | func GeneratePreKeys(start int, count int) ([]*record.PreKey, error) { 34 | var preKeys []*record.PreKey 35 | 36 | for i := start; i <= count; i++ { 37 | key, err := ecc.GenerateKeyPair() 38 | if err != nil { 39 | return nil, err 40 | } 41 | preKeys = append(preKeys, record.NewPreKey(uint32(i), key)) 42 | } 43 | 44 | return preKeys, nil 45 | } 46 | 47 | // GenerateLastResortKey will generate the last resort PreKey. Clients should 48 | // do this only once, at install time, and durably store it for the length 49 | // of the install. 50 | func GenerateLastResortKey() (*record.PreKey, error) { 51 | keyPair, err := ecc.GenerateKeyPair() 52 | if err != nil { 53 | return nil, err 54 | } 55 | return record.NewPreKey(0, keyPair), nil 56 | } 57 | 58 | // GenerateSignedPreKey generates a signed PreKey. 59 | func GenerateSignedPreKey(identityKeyPair *identity.KeyPair, signedPreKeyID uint32) (*record.SignedPreKey, error) { 60 | keyPair, err := ecc.GenerateKeyPair() 61 | if err != nil { 62 | return nil, err 63 | } 64 | signature, e := ecc.CalculateSignature(identityKeyPair.PrivateKey(), keyPair.PublicKey().Serialize()) 65 | if e != nil { 66 | return nil, e 67 | } 68 | timestamp := time.Now().Unix() 69 | 70 | return record.NewSignedPreKey(signedPreKeyID, timestamp, keyPair, signature), nil 71 | } 72 | 73 | // GenerateRegistrationID generates a registration ID. Clients should only do 74 | // this once, at install time. 75 | func GenerateRegistrationID() uint32 { 76 | var n uint32 77 | binary.Read(rand.Reader, binary.LittleEndian, &n) 78 | 79 | return n 80 | } 81 | 82 | //---------- Group Stuff ---------------- 83 | 84 | func GenerateSenderSigningKey() (*ecc.ECKeyPair, error) { 85 | return ecc.GenerateKeyPair() 86 | } 87 | 88 | func GenerateSenderKey() []byte { 89 | randBytes := make([]byte, 32) 90 | rand.Read(randBytes) 91 | return randBytes 92 | } 93 | 94 | func GenerateSenderKeyID() uint32 { 95 | return GenerateRegistrationID() 96 | } 97 | 98 | //---------- End Group Stuff -------------- 99 | -------------------------------------------------------------------------------- /signal/cipher/cipher.go: -------------------------------------------------------------------------------- 1 | // Package cipher is a package for common encrypt/decrypt of symmetric key messages. 2 | package cipher 3 | 4 | import ( 5 | "bytes" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "errors" 9 | ) 10 | 11 | // Decrypt will use the given key, iv, and ciphertext and return 12 | // the plaintext bytes. 13 | func Decrypt(iv, key, ciphertext []byte) ([]byte, error) { 14 | block, err := aes.NewCipher(key) 15 | if err != nil { 16 | return nil, err 17 | } 18 | if len(ciphertext) < aes.BlockSize { 19 | return nil, errors.New("ciphertext too short") 20 | } 21 | cbc := cipher.NewCBCDecrypter(block, iv) 22 | cbc.CryptBlocks(ciphertext, ciphertext) 23 | 24 | unpaddedText, err := pkcs7Unpad(ciphertext, aes.BlockSize) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return unpaddedText, nil 30 | } 31 | 32 | // Encrypt will use the given iv, key, and plaintext bytes 33 | // and return ciphertext bytes. 34 | func Encrypt(iv, key, plaintext []byte) ([]byte, error) { 35 | block, err := aes.NewCipher(key) 36 | if err != nil { 37 | return nil, err 38 | } 39 | paddedText, err := pkcs7Pad(plaintext, block.BlockSize()) 40 | if err != nil { 41 | return nil, err 42 | } 43 | ciphertext := make([]byte, len(paddedText)) 44 | mode := cipher.NewCBCEncrypter(block, iv) 45 | mode.CryptBlocks(ciphertext, paddedText) 46 | 47 | return ciphertext, nil 48 | } 49 | 50 | // PKCS7 padding. 51 | 52 | // PKCS7 errors. 53 | var ( 54 | // ErrInvalidBlockSize indicates hash blocksize <= 0. 55 | ErrInvalidBlockSize = errors.New("invalid blocksize") 56 | 57 | // ErrInvalidPKCS7Data indicates bad input to PKCS7 pad or unpad. 58 | ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)") 59 | 60 | // ErrInvalidPKCS7Padding indicates PKCS7 unpad fails to bad input. 61 | ErrInvalidPKCS7Padding = errors.New("invalid padding on input") 62 | ) 63 | 64 | // pkcs7Pad right-pads the given byte slice with 1 to n bytes, where 65 | // n is the block size. The size of the result is x times n, where x 66 | // is at least 1. 67 | func pkcs7Pad(b []byte, blocksize int) ([]byte, error) { 68 | if blocksize <= 0 { 69 | return nil, ErrInvalidBlockSize 70 | } 71 | if b == nil || len(b) == 0 { 72 | return nil, ErrInvalidPKCS7Data 73 | } 74 | n := blocksize - (len(b) % blocksize) 75 | pb := make([]byte, len(b)+n) 76 | copy(pb, b) 77 | copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) 78 | return pb, nil 79 | } 80 | 81 | // pkcs7Unpad validates and unpads data from the given bytes slice. 82 | // The returned value will be 1 to n bytes smaller depending on the 83 | // amount of padding, where n is the block size. 84 | func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) { 85 | if blocksize <= 0 { 86 | return nil, ErrInvalidBlockSize 87 | } 88 | if b == nil || len(b) == 0 { 89 | return nil, ErrInvalidPKCS7Data 90 | } 91 | if len(b)%blocksize != 0 { 92 | return nil, ErrInvalidPKCS7Padding 93 | } 94 | c := b[len(b)-1] 95 | n := int(c) 96 | if n == 0 || n > len(b) { 97 | return nil, ErrInvalidPKCS7Padding 98 | } 99 | for i := 0; i < n; i++ { 100 | if b[len(b)-n+i] != c { 101 | return nil, ErrInvalidPKCS7Padding 102 | } 103 | } 104 | return b[:len(b)-n], nil 105 | } 106 | -------------------------------------------------------------------------------- /signal/cipher/cbc.go: -------------------------------------------------------------------------------- 1 | /* 2 | CBC describes a block cipher mode. In cryptography, a block cipher mode of operation is an algorithm that uses a 3 | block cipher to provide an information service such as confidentiality or authenticity. A block cipher by itself 4 | is only suitable for the secure cryptographic transformation (encryption or decryption) of one fixed-length group of 5 | bits called a block. A mode of operation describes how to repeatedly apply a cipher's single-block operation to 6 | securely transform amounts of data larger than a block. 7 | 8 | This package simplifies the usage of AES-256-CBC. 9 | */ 10 | package cipher 11 | 12 | /* 13 | Some code is provided by the GitHub user locked (github.com/locked): 14 | https://gist.github.com/locked/b066aa1ddeb2b28e855e 15 | Thanks! 16 | */ 17 | import ( 18 | "bytes" 19 | "crypto/aes" 20 | "crypto/cipher" 21 | "crypto/rand" 22 | "fmt" 23 | "io" 24 | ) 25 | 26 | /* 27 | Decrypt is a function that decrypts a given cipher text with a provided key and initialization vector(iv). 28 | */ 29 | func DecryptCbc(iv, key, ciphertext []byte) ([]byte, error) { 30 | block, err := aes.NewCipher(key) 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if len(ciphertext) < aes.BlockSize { 37 | return nil, fmt.Errorf("ciphertext is shorter then block size: %d / %d", len(ciphertext), aes.BlockSize) 38 | } 39 | 40 | if iv == nil { 41 | iv = ciphertext[:aes.BlockSize] 42 | ciphertext = ciphertext[aes.BlockSize:] 43 | } 44 | 45 | cbc := cipher.NewCBCDecrypter(block, iv) 46 | cbc.CryptBlocks(ciphertext, ciphertext) 47 | 48 | return unpad(ciphertext) 49 | } 50 | 51 | /* 52 | Encrypt is a function that encrypts plaintext with a given key and an optional initialization vector(iv). 53 | */ 54 | func EncryptCbc(iv, key, plaintext []byte) ([]byte, error) { 55 | plaintext = pad(plaintext, aes.BlockSize) 56 | 57 | if len(plaintext)%aes.BlockSize != 0 { 58 | return nil, fmt.Errorf("plaintext is not a multiple of the block size: %d / %d", len(plaintext), aes.BlockSize) 59 | } 60 | 61 | block, err := aes.NewCipher(key) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | var ciphertext []byte 67 | if iv == nil { 68 | ciphertext = make([]byte, aes.BlockSize+len(plaintext)) 69 | iv := ciphertext[:aes.BlockSize] 70 | if _, err := io.ReadFull(rand.Reader, iv); err != nil { 71 | return nil, err 72 | } 73 | 74 | cbc := cipher.NewCBCEncrypter(block, iv) 75 | cbc.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) 76 | } else { 77 | ciphertext = make([]byte, len(plaintext)) 78 | 79 | cbc := cipher.NewCBCEncrypter(block, iv) 80 | cbc.CryptBlocks(ciphertext, plaintext) 81 | } 82 | 83 | return ciphertext, nil 84 | } 85 | 86 | func pad(ciphertext []byte, blockSize int) []byte { 87 | padding := blockSize - len(ciphertext)%blockSize 88 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 89 | return append(ciphertext, padtext...) 90 | } 91 | 92 | func unpad(src []byte) ([]byte, error) { 93 | length := len(src) 94 | padLen := int(src[length-1]) 95 | 96 | if padLen > length { 97 | return nil, fmt.Errorf("padding is greater then the length: %d / %d", padLen, length) 98 | } 99 | 100 | return src[:(length - padLen)], nil 101 | } 102 | -------------------------------------------------------------------------------- /signal/state/record/state_pending_key_exchange.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/keys/identity" 6 | "wa/signal/pb" 7 | "wa/signal/util/bytehelper" 8 | 9 | "google.golang.org/protobuf/proto" 10 | ) 11 | 12 | // NewPendingKeyExchange will return a new PendingKeyExchange object. 13 | func NewPendingKeyExchange(sequence uint32, localBaseKeyPair, localRatchetKeyPair *ecc.ECKeyPair, 14 | localIdentityKeyPair *identity.KeyPair) *PendingKeyExchange { 15 | 16 | return &PendingKeyExchange{ 17 | sequence: sequence, 18 | localBaseKeyPair: localBaseKeyPair, 19 | localRatchetKeyPair: localRatchetKeyPair, 20 | localIdentityKeyPair: localIdentityKeyPair, 21 | } 22 | } 23 | 24 | // NewPendingKeyExchangeFromStruct will return a PendingKeyExchange object from 25 | // the given structure. This is used to get a deserialized pending prekey exchange 26 | // fetched from persistent storage. 27 | func NewPendingKeyExchangeFromStruct(structure *pb.SessionStructure_PendingKeyExchange) *PendingKeyExchange { 28 | // Return nil if no structure was provided. 29 | if structure == nil { 30 | return nil 31 | } 32 | 33 | // Alias the SliceToArray method. 34 | getArray := bytehelper.SliceToArray 35 | 36 | // Convert the bytes in the given structure to ECC objects. 37 | localBaseKeyPair := ecc.NewECKeyPair( 38 | ecc.NewDjbECPublicKey(structure.LocalBaseKey), 39 | ecc.NewDjbECPrivateKey(getArray(structure.LocalBaseKeyPrivate)), 40 | ) 41 | localRatchetKeyPair := ecc.NewECKeyPair( 42 | ecc.NewDjbECPublicKey(structure.LocalRatchetKey), 43 | ecc.NewDjbECPrivateKey(getArray(structure.LocalRatchetKeyPrivate)), 44 | ) 45 | localIdentityKeyPair := identity.NewKeyPair( 46 | identity.NewKey(ecc.NewDjbECPublicKey(structure.LocalIdentityKey)), 47 | ecc.NewDjbECPrivateKey(getArray(structure.LocalIdentityKeyPrivate)), 48 | ) 49 | 50 | // Return the PendingKeyExchange with the deserialized keys. 51 | return &PendingKeyExchange{ 52 | sequence: structure.GetSequence(), 53 | localBaseKeyPair: localBaseKeyPair, 54 | localRatchetKeyPair: localRatchetKeyPair, 55 | localIdentityKeyPair: localIdentityKeyPair, 56 | } 57 | } 58 | 59 | // PendingKeyExchange is a structure for storing a pending 60 | // key exchange for a session state. 61 | type PendingKeyExchange struct { 62 | sequence uint32 63 | localBaseKeyPair *ecc.ECKeyPair 64 | localRatchetKeyPair *ecc.ECKeyPair 65 | localIdentityKeyPair *identity.KeyPair 66 | } 67 | 68 | // structure will return a serializable structure of a pending key exchange 69 | // so it can be persistently stored. 70 | func (p *PendingKeyExchange) structure() *pb.SessionStructure_PendingKeyExchange { 71 | getSlice := bytehelper.ArrayToSlice 72 | return &pb.SessionStructure_PendingKeyExchange{ 73 | Sequence: proto.Uint32(p.sequence), 74 | LocalBaseKey: getSlice(p.localBaseKeyPair.PublicKey().PublicKey()), 75 | LocalBaseKeyPrivate: getSlice(p.localBaseKeyPair.PrivateKey().Serialize()), 76 | LocalRatchetKey: getSlice(p.localRatchetKeyPair.PublicKey().PublicKey()), 77 | LocalRatchetKeyPrivate: getSlice(p.localRatchetKeyPair.PrivateKey().Serialize()), 78 | LocalIdentityKey: getSlice(p.localIdentityKeyPair.PublicKey().PublicKey().PublicKey()), 79 | LocalIdentityKeyPrivate: getSlice(p.localIdentityKeyPair.PrivateKey().Serialize()), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /signal/ecc/sign_curve25519.go: -------------------------------------------------------------------------------- 1 | package ecc 2 | 3 | // Package curve25519sign implements a signature scheme based on Curve25519 keys. 4 | // See https://moderncrypto.org/mail-archive/curves/2014/000205.html for details. 5 | 6 | import ( 7 | "crypto/ed25519" 8 | 9 | "wa/signal/ecc/edwards25519" 10 | ) 11 | 12 | /* 13 | 14 | This is due to different ed25519 private key formats. An ed25519 key starts out as a 32 byte seed. This seed is hashed 15 | with SHA512 to produce 64 bytes (a couple of bits are flipped too). The first 32 bytes of these are used to generate 16 | the public key (which is also 32 bytes), and the last 32 bytes are used in the generation of the signature. 17 | 18 | The Golang private key format is the 32 byte seed concatenated with the 32 byte public key. 19 | The private keys we are using are the 64 byte result of the hash 20 | (or possibly just 64 random bytes that are used the same way as the hash result). 21 | 22 | Since it’s not possible to reverse the hash, you can’t convert the 64 byte keys to a format that the Golang API 23 | will accept. 24 | 25 | You can produce a version of the Golang lib based on the existing package. 26 | 27 | The following code depends on the internal package golang.org/x/crypto/ed25519/internal/edwards25519, 28 | so if you want to use it you will need to copy that package out so that it is available to you code. 29 | It’s also very “rough and ready”, I’ve basically just copied the chunks of code needed 30 | from the existing code to get this to work. 31 | 32 | */ 33 | 34 | // sign signs the message with privateKey and returns a signature as a byte slice. 35 | func sign(privateKey *[32]byte, message []byte, random [64]byte) *[64]byte { 36 | panic(`replaced by xed25519.Sign`) 37 | return nil 38 | } 39 | 40 | // verify checks whether the message has a valid signature. 41 | func verify(publicKey [32]byte, message []byte, signature *[64]byte) bool { 42 | 43 | publicKey[31] &= 0x7F 44 | 45 | /* Convert the Curve25519 public key into an Ed25519 public key. In 46 | particular, convert Curve25519's "montgomery" x-coordinate into an 47 | Ed25519 "edwards" y-coordinate: 48 | 49 | ed_y = (mont_x - 1) / (mont_x + 1) 50 | 51 | NOTE: mont_x=-1 is converted to ed_y=0 since fe_invert is mod-exp 52 | 53 | Then move the sign bit into the pubkey from the signature. 54 | */ 55 | 56 | var edY, one, montX, montXMinusOne, montXPlusOne edwards25519.FieldElement 57 | edwards25519.FeFromBytes(&montX, &publicKey) 58 | edwards25519.FeOne(&one) 59 | edwards25519.FeSub(&montXMinusOne, &montX, &one) 60 | edwards25519.FeAdd(&montXPlusOne, &montX, &one) 61 | edwards25519.FeInvert(&montXPlusOne, &montXPlusOne) 62 | edwards25519.FeMul(&edY, &montXMinusOne, &montXPlusOne) 63 | 64 | var A_ed [32]byte 65 | edwards25519.FeToBytes(&A_ed, &edY) 66 | 67 | A_ed[31] |= signature[63] & 0x80 68 | signature[63] &= 0x7F 69 | 70 | sig := *signature 71 | 72 | return ed25519.Verify(A_ed[:], message, sig[:]) 73 | } 74 | 75 | // sign32 signs the message with privateKey and returns a signature as a byte slice. 76 | func sign32(privateKey [32]byte, message []byte, random [64]byte) (signature [64]byte) { 77 | pk := [64]byte{} 78 | copy(pk[:], privateKey[:32]) 79 | sig := ed25519.Sign(pk[:], message) 80 | copy(signature[:], sig[:]) 81 | 82 | return signature 83 | } 84 | 85 | // verify32 checks whether the message has a valid signature. 86 | func verify32(publicKey [32]byte, message []byte, signature [64]byte) bool { 87 | return ed25519.Verify(publicKey[:], message, signature[:]) 88 | } 89 | -------------------------------------------------------------------------------- /rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | 7 | "ajson" 8 | "phoenix" 9 | "wa/core" 10 | "wa/def" 11 | "wa/rpc/pb" 12 | "wa/xmpp" 13 | 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | type WaServer struct { 18 | pb.UnimplementedRpcServer 19 | } 20 | 21 | // required field: `acc`, `func` 22 | func (s *WaServer) Exec(ctx context.Context, in *pb.Json) (ret *pb.Json, e error) { 23 | defer phoenix.Ignore(func() { 24 | ret = &pb.Json{Data: core.NewCrashRet().ToString()} 25 | }) 26 | 27 | j, e := ajson.Parse(in.GetData()) 28 | if e != nil { 29 | e = errors.Wrap(e, `Fail parse input json`) 30 | return 31 | } 32 | 33 | func_ := j.Get(`func`).String() 34 | 35 | // check if core.xxx exists 36 | fn := reflect.ValueOf(&core.CORE).MethodByName(func_) 37 | if !fn.IsValid() { 38 | e = errors.New(`invalid func: ` + func_) 39 | return 40 | } 41 | 42 | // call core.xxx functions 43 | args := []reflect.Value{reflect.ValueOf(j)} 44 | r := fn.Call(args) 45 | rj := r[0].Interface().(*ajson.Json) 46 | 47 | ret = &pb.Json{Data: rj.ToString()} 48 | return 49 | } 50 | 51 | func (s *WaServer) Push(in *pb.Json, stream pb.Rpc_PushServer) (e error) { 52 | defer phoenix.Ignore(func() { 53 | e = errors.New("Push crashed") 54 | }) 55 | 56 | j, e := ajson.Parse(in.GetData()) 57 | if e != nil { 58 | e = errors.Wrap(e, `Fail parse input json`) 59 | return 60 | } 61 | // get acc 62 | a, e := core.GetAccFromJson(j) 63 | if e != nil { 64 | return 65 | } 66 | 67 | a.Log.Debug("Push.1 try a.Lock") 68 | a.Lock() 69 | a.Log.Debug("Push.2 a.Lock ok") 70 | 71 | // prevent connect multiple push rpc 72 | if a.PushStreamConnected { 73 | e = errors.New("acc.pushStream already exists") 74 | a.UnLock() // important !!!!!!!!! 75 | return 76 | } 77 | 78 | a.Log.Debug("new Push stream connected") 79 | defer a.Log.Debug("Push stream end") 80 | 81 | a.Wg.Add(1) 82 | defer a.Wg.Done() 83 | 84 | // push notification 85 | push_to_client := func(args ...any) error { 86 | j := args[0].(*xmpp.Node).ToJson() 87 | e := stream.Send(&pb.Json{ 88 | Data: core.NewJsonRet(j).ToString(), 89 | }) 90 | if e != nil { 91 | a.Log.Error("stream push fail: " + e.Error()) 92 | } 93 | return nil 94 | } 95 | sub := a.Event.On(def.Ev_Push, push_to_client) 96 | defer a.Event.Un(sub) 97 | 98 | // the long link disconnects when this function returns 99 | // so don't return, blocking to keep alive 100 | // until client disconnected or AccOff() 101 | stm := NewStream(stream) 102 | 103 | // disconnected 104 | disconnected := func(args ...any) error { 105 | a.Log.Debug("push 'disconnect' to client") 106 | // send last error msg 107 | stream.Send(&pb.Json{ 108 | Data: core.NewErrRet(args[0].(error)).ToString(), 109 | }) 110 | return nil 111 | } 112 | sub_1 := a.Event.On(def.Ev_NoiseDisconnected, disconnected) 113 | defer a.Event.Un(sub_1) 114 | 115 | // close only when AccOff 116 | close := func(...any) error { 117 | a.Log.Debug("on acc_off, close stream") 118 | stm.Close() 119 | return nil 120 | } 121 | sub_2 := a.Event.On(def.Ev_AccOff, close) 122 | defer a.Event.Un(sub_2) 123 | 124 | a.PushStreamConnected = true 125 | defer func() { a.PushStreamConnected = false }() 126 | 127 | // done event binding, release the event lock 128 | a.UnLock() 129 | 130 | a.Log.Debug("stream KeepAlive()") 131 | stm.KeepAlive() // blocking, wait for stm.Close() 132 | 133 | a.Log.Info("push closed") 134 | 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /core/vname.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "ahex" 5 | "ajson" 6 | "algo/xed25519" 7 | "arand" 8 | "wa/pb" 9 | "wa/xmpp" 10 | 11 | "github.com/pkg/errors" 12 | "google.golang.org/protobuf/proto" 13 | ) 14 | 15 | func (a *Acc) load_vname(include_nick, include_sig bool) (*pb.VName, error) { 16 | prof, e := a.Store.GetProfile() 17 | if e != nil { 18 | return nil, errors.Wrap(e, "fail get profile") 19 | } 20 | 21 | cfg, e := a.Store.GetConfig() 22 | if e != nil { 23 | return nil, errors.Wrap(e, "fail get config") 24 | } 25 | vname := &pb.VName{} 26 | e = proto.Unmarshal(cfg.VNameCert, vname) 27 | if e != nil { 28 | return nil, errors.Wrap(e, `wrong cfg.VNameCert: `+ahex.Enc(cfg.VNameCert)) 29 | } 30 | if include_nick { 31 | if vname.Cert == nil { 32 | vname.Cert = &pb.VName_CERT{} 33 | } 34 | vname.Cert.Nick = proto.String(prof.Nick) 35 | } 36 | 37 | if include_sig { 38 | e = a.sign_vname(vname) 39 | if e != nil { 40 | return nil, errors.Wrap(e, "fail sign vname") 41 | } 42 | } 43 | return vname, nil 44 | } 45 | 46 | // only for Register() 47 | func (a *Acc) generate_vname() (*pb.VName, error) { 48 | vname := &pb.VName{ 49 | Cert: &pb.VName_CERT{ 50 | Rand_64: proto.Uint64(uint64(arand.Int(0x1000000000000000, 0x7fffffffffffffff))), 51 | SmbWa: proto.String("smb:wa"), 52 | Nick: proto.String(""), 53 | }, 54 | } 55 | e := a.sign_vname(vname) 56 | return vname, e 57 | } 58 | func (a *Acc) sign_vname(vname *pb.VName) error { 59 | bs, _ := proto.Marshal(vname.Cert) 60 | 61 | iden, e := a.Store.GetMyIdentity() 62 | if e != nil { 63 | return e 64 | } 65 | vname.Signature, e = xed25519.Sign(iden.PrivateKey, bs) 66 | return e 67 | } 68 | 69 | func (a *Acc) set_biz_verified_name() error { 70 | vname, e := a.load_vname(true, true) 71 | if e != nil { 72 | return e 73 | } 74 | 75 | bs, _ := proto.Marshal(vname) 76 | 77 | _, e = a.Noise.WriteReadXmppNode(&xmpp.Node{ 78 | Tag: `iq`, 79 | Attrs: []*xmpp.KeyValue{ 80 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 81 | {Key: `to`, Value: `s.whatsapp.net`}, 82 | {Key: `type`, Value: `set`}, 83 | {Key: `xmlns`, Value: `w:biz`}, 84 | }, 85 | Children: []*xmpp.Node{ 86 | { 87 | Tag: `verified_name`, 88 | Attrs: []*xmpp.KeyValue{ 89 | {Key: `v`, Value: `2`}, 90 | }, 91 | Data: bs, 92 | }, 93 | }, 94 | }) 95 | return e 96 | } 97 | func (c Core) SetBizVerifiedName(j *ajson.Json) *ajson.Json { 98 | a, e := GetAccFromJson(j) 99 | if e != nil { 100 | return NewErrRet(e) 101 | } 102 | 103 | // 1. save to db 104 | rj := c.SetMyProfile(j) 105 | if rj.Get(`ErrCode`).Int() != 0 { 106 | return rj 107 | } 108 | 109 | // 2. commit 110 | e = a.set_biz_verified_name() 111 | if e != nil { 112 | return NewErrRet(e) 113 | } 114 | return NewSucc() 115 | } 116 | 117 | func (a *Acc) get_biz_verified_name() error { 118 | dev, e := a.Store.GetDev() 119 | if e != nil { 120 | return e 121 | } 122 | 123 | _, e = a.Noise.WriteReadXmppNode(&xmpp.Node{ 124 | Tag: `iq`, 125 | Attrs: []*xmpp.KeyValue{ 126 | {Key: `id`, Value: a.Noise.NextIqId_2()}, 127 | {Key: `type`, Value: `get`}, 128 | {Key: `xmlns`, Value: `w:biz`}, 129 | }, 130 | Children: []*xmpp.Node{ 131 | { 132 | Tag: `verified_name`, 133 | Attrs: []*xmpp.KeyValue{ 134 | {Key: `jid`, Value: dev.Cc + dev.Phone + "@s.whatsapp.net"}, 135 | }, 136 | }, 137 | }, 138 | }) 139 | return e 140 | } 141 | func (c Core) GetBizVerifiedName(j *ajson.Json) *ajson.Json { 142 | a, e := GetAccFromJson(j) 143 | if e != nil { 144 | return NewErrRet(e) 145 | } 146 | e = a.get_biz_verified_name() 147 | if e != nil { 148 | return NewErrRet(e) 149 | } 150 | return NewSucc() 151 | } 152 | -------------------------------------------------------------------------------- /signal/pb/LocalStorageProtocol.proto: -------------------------------------------------------------------------------- 1 | package pb; 2 | 3 | option go_package = ".;pb"; 4 | option java_package = "org.whispersystems.libsignal.state"; 5 | option java_outer_classname = "StorageProtos"; 6 | 7 | message SessionStructure { 8 | message Chain { 9 | optional bytes senderRatchetKey = 1; 10 | optional bytes senderRatchetKeyPrivate = 2; 11 | 12 | message ChainKey { 13 | optional uint32 index = 1; 14 | optional bytes key = 2; 15 | } 16 | 17 | optional ChainKey chainKey = 3; 18 | 19 | message MessageKey { 20 | optional uint32 index = 1; 21 | optional bytes cipherKey = 2; 22 | optional bytes macKey = 3; 23 | optional bytes iv = 4; 24 | } 25 | 26 | repeated MessageKey messageKeys = 4; 27 | } 28 | 29 | message PendingKeyExchange { 30 | optional uint32 sequence = 1; 31 | optional bytes localBaseKey = 2; 32 | optional bytes localBaseKeyPrivate = 3; 33 | optional bytes localRatchetKey = 4; 34 | optional bytes localRatchetKeyPrivate = 5; 35 | optional bytes localIdentityKey = 7; 36 | optional bytes localIdentityKeyPrivate = 8; 37 | } 38 | 39 | message PendingPreKey { 40 | optional uint32 preKeyId = 1; 41 | optional int32 signedPreKeyId = 3; 42 | optional bytes baseKey = 2; 43 | } 44 | 45 | optional uint32 sessionVersion = 1; 46 | optional bytes localIdentityPublic = 2; 47 | optional bytes remoteIdentityPublic = 3; 48 | 49 | optional bytes rootKey = 4; 50 | optional uint32 previousCounter = 5; 51 | 52 | optional Chain senderChain = 6; 53 | repeated Chain receiverChains = 7; 54 | 55 | optional PendingKeyExchange pendingKeyExchange = 8; 56 | optional PendingPreKey pendingPreKey = 9; 57 | 58 | optional uint32 remoteRegistrationId = 10; 59 | optional uint32 localRegistrationId = 11; 60 | 61 | optional bool needsRefresh = 12; 62 | optional bytes aliceBaseKey = 13; // SenderBaseKey 63 | } 64 | 65 | message RecordStructure { 66 | optional SessionStructure currentSession = 1; // SessionState 67 | repeated SessionStructure previousSessions = 2; // PreviousStates 68 | } 69 | 70 | message PreKeyRecordStructure { 71 | optional uint32 id = 1; 72 | optional bytes publicKey = 2; 73 | optional bytes privateKey = 3; 74 | } 75 | 76 | message SignedPreKeyRecordStructure { 77 | optional uint32 id = 1; 78 | optional bytes publicKey = 2; 79 | optional bytes privateKey = 3; 80 | optional bytes signature = 4; 81 | optional fixed64 timestamp = 5; 82 | } 83 | 84 | message IdentityKeyPairStructure { 85 | optional bytes publicKey = 1; 86 | optional bytes privateKey = 2; 87 | } 88 | 89 | message SenderKeyStateStructure { 90 | message SenderChainKey { 91 | optional uint32 iteration = 1; 92 | optional bytes seed = 2; // chainKey 93 | } 94 | 95 | message SenderMessageKey { 96 | optional uint32 iteration = 1; 97 | optional bytes seed = 2; 98 | } 99 | 100 | message SenderSigningKey { 101 | optional bytes public = 1; 102 | optional bytes private = 2; 103 | } 104 | 105 | optional uint32 senderKeyId = 1; // KeyID 106 | optional SenderChainKey senderChainKey = 2; 107 | optional SenderSigningKey senderSigningKey = 3; 108 | repeated SenderMessageKey senderMessageKeys = 4; // Keys 109 | } 110 | 111 | message SenderKeyRecordStructure { 112 | repeated SenderKeyStateStructure senderKeyStates = 1; 113 | } -------------------------------------------------------------------------------- /signal/protocol/sender_key_distribution_message.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | 7 | "wa/signal/ecc" 8 | "wa/signal/pb" 9 | 10 | "github.com/golang/protobuf/proto" 11 | ) 12 | 13 | func NewSenderKeyDistributionMessageFromBytes( 14 | data []byte, 15 | ) (*SenderKeyDistributionMessage, error) { 16 | if len(data) < 2 { 17 | return nil, errors.New(`skdm too short`) 18 | } 19 | 20 | p := &pb.SenderKeyDistributionMessage{} 21 | e := proto.Unmarshal(data[1:], p) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | ver, e := strconv.Atoi(string(data[0:1])) 27 | if e != nil { 28 | return nil, e 29 | } 30 | stru := &SenderKeyDistributionMessage{ 31 | P: p, 32 | version: uint32(ver), 33 | } 34 | 35 | e = verify_sender_key_distribution_message(stru) 36 | if e != nil { 37 | return nil, e 38 | } 39 | return stru, nil 40 | } 41 | 42 | func verify_sender_key_distribution_message( 43 | structure *SenderKeyDistributionMessage, 44 | ) error { 45 | 46 | // Throw an error if the given message structure is an unsupported version. 47 | if structure.version <= UnsupportedVersion { 48 | err := "Legacy message: " + strconv.Itoa(int(structure.version)) 49 | return errors.New(err) 50 | } 51 | 52 | // Throw an error if the given message structure is a future version. 53 | if structure.version > CurrentVersion { 54 | err := "Unknown version: " + strconv.Itoa(int(structure.version)) 55 | return errors.New(err) 56 | } 57 | 58 | // Throw an error if the structure is missing critical fields. 59 | if len(structure.P.GetSigningKey()) == 0 || len(structure.P.GetChainKey()) == 0 { 60 | err := "Incomplete message." 61 | return errors.New(err) 62 | } 63 | 64 | // Get the signing key object from bytes. 65 | _, err := ecc.DecodePoint(structure.P.GetSigningKey(), 0) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | // NewSenderKeyDistributionMessage returns a Signal Ciphertext message. 74 | func NewSenderKeyDistributionMessage( 75 | id uint32, iteration uint32, 76 | chainKey []byte, signatureKey ecc.ECPublicKeyable, 77 | ) *SenderKeyDistributionMessage { 78 | 79 | k := signatureKey.PublicKey() 80 | return &SenderKeyDistributionMessage{ 81 | version: CurrentVersion, 82 | P: &pb.SenderKeyDistributionMessage{ 83 | Id: &id, 84 | Iteration: &iteration, 85 | ChainKey: chainKey, 86 | SigningKey: k[:], 87 | }, 88 | } 89 | } 90 | 91 | type SenderKeyDistributionMessage struct { 92 | version uint32 93 | P *pb.SenderKeyDistributionMessage 94 | } 95 | 96 | func (p *SenderKeyDistributionMessage) ID() uint32 { 97 | return p.P.GetId() 98 | } 99 | 100 | func (p *SenderKeyDistributionMessage) Iteration() uint32 { 101 | return p.P.GetIteration() 102 | } 103 | 104 | func (p *SenderKeyDistributionMessage) ChainKey() []byte { 105 | return p.P.GetChainKey() 106 | } 107 | 108 | func (p *SenderKeyDistributionMessage) SignatureKey() ecc.ECPublicKeyable { 109 | signingKey, err := ecc.DecodePoint(p.P.GetSigningKey(), 0) 110 | if err != nil { 111 | return nil 112 | } 113 | 114 | return signingKey 115 | } 116 | 117 | func (p *SenderKeyDistributionMessage) Serialize() []byte { 118 | // make a copy, the SingingKey should begin with '05' 119 | p2 := &pb.SenderKeyDistributionMessage{ 120 | Id: p.P.Id, 121 | Iteration: p.P.Iteration, 122 | ChainKey: p.P.ChainKey, 123 | SigningKey: append([]byte{ecc.DjbType}, p.P.SigningKey...), 124 | } 125 | 126 | // version + proto 127 | bs, e := proto.Marshal(p2) 128 | if e != nil { 129 | return nil 130 | } 131 | ver := []byte(strconv.Itoa(int(p.version))) 132 | ret := append(ver, bs...) 133 | return ret 134 | } 135 | 136 | // Type will return the message's type. 137 | func (p *SenderKeyDistributionMessage) Type() uint32 { 138 | return SENDERKEY_DISTRIBUTION_TYPE 139 | } 140 | -------------------------------------------------------------------------------- /signal/groups/state/record/sender_key.go: -------------------------------------------------------------------------------- 1 | package record 2 | 3 | import ( 4 | "errors" 5 | 6 | "wa/signal/ecc" 7 | "wa/signal/pb" 8 | 9 | "github.com/golang/protobuf/proto" 10 | ) 11 | 12 | func NewSenderKeyFromBytes(data []byte) (*SenderKey, error) { 13 | p := &pb.SenderKeyRecordStructure{} 14 | e := proto.Unmarshal(data, p) 15 | if e != nil { 16 | return nil, e 17 | } 18 | 19 | // Build our sender key states from structure. 20 | senderKeyStates := make([]*SenderKeyState, len(p.SenderKeyStates)) 21 | for i := range p.SenderKeyStates { 22 | var err error 23 | senderKeyStates[i], err = NewSenderKeyStateFromStructure(p.SenderKeyStates[i]) 24 | if err != nil { 25 | return nil, err 26 | } 27 | } 28 | 29 | // Build and return our session. 30 | senderKey := &SenderKey{ 31 | senderKeyStates: senderKeyStates, 32 | } 33 | 34 | return senderKey, nil 35 | 36 | } 37 | 38 | // NewSenderKey record returns a new sender key record that can 39 | // be stored in a SenderKeyStore. 40 | func NewSenderKey() *SenderKey { 41 | return &SenderKey{ 42 | senderKeyStates: []*SenderKeyState{}, 43 | } 44 | } 45 | 46 | // SenderKey record is a structure for storing pre keys inside 47 | // a SenderKeyStore. 48 | type SenderKey struct { 49 | senderKeyStates []*SenderKeyState 50 | } 51 | 52 | // SenderKeyState will return the first sender key state in the record's 53 | // list of sender key states. 54 | func (k *SenderKey) SenderKeyState() (*SenderKeyState, error) { 55 | if len(k.senderKeyStates) > 0 { 56 | return k.senderKeyStates[0], nil 57 | } 58 | return nil, errors.New("No Sender Keys") 59 | } 60 | 61 | // GetSenderKeyStateByID will return the sender key state with the given 62 | // key id. 63 | func (k *SenderKey) GetSenderKeyStateByID(keyID uint32) (*SenderKeyState, error) { 64 | for i := 0; i < len(k.senderKeyStates); i++ { 65 | if k.senderKeyStates[i].KeyID() == keyID { 66 | return k.senderKeyStates[i], nil 67 | } 68 | } 69 | 70 | return nil, errors.New("No sender key for for ID") 71 | } 72 | 73 | // IsEmpty will return false if there is more than one state in this 74 | // senderkey record. 75 | func (k *SenderKey) IsEmpty() bool { 76 | return len(k.senderKeyStates) == 0 77 | } 78 | 79 | // AddSenderKeyState will add a new state to this senderkey record with the given 80 | // id, iteration, chainkey, and signature key. 81 | func (k *SenderKey) AddSenderKeyState( 82 | id uint32, iteration uint32, 83 | chainKey []byte, signatureKey ecc.ECPublicKeyable, 84 | ) { 85 | 86 | newState := NewSenderKeyStateFromPublicKey(id, iteration, chainKey, signatureKey) 87 | k.senderKeyStates = append(k.senderKeyStates, newState) 88 | 89 | if len(k.senderKeyStates) > maxMessageKeys { 90 | k.senderKeyStates = k.senderKeyStates[1:] 91 | } 92 | } 93 | 94 | // SetSenderKeyState will replace the current senderkey states with the given 95 | // senderkey state. 96 | func (k *SenderKey) SetSenderKeyState( 97 | id uint32, iteration uint32, 98 | chainKey []byte, signatureKey *ecc.ECKeyPair, 99 | ) { 100 | 101 | newState := NewSenderKeyState(id, iteration, chainKey, signatureKey) 102 | k.senderKeyStates = make([]*SenderKeyState, 0, maxMessageKeys/2) 103 | k.senderKeyStates = append(k.senderKeyStates, newState) 104 | } 105 | 106 | // Serialize will return the record as serialized bytes so it can be 107 | // persistently stored. 108 | func (k *SenderKey) Serialize() ([]byte, error) { 109 | p := k.structure() 110 | return proto.Marshal(p) 111 | } 112 | 113 | // structure will return a simple serializable record structure. 114 | // This is used for serialization to persistently 115 | // store a session record. 116 | func (k *SenderKey) structure() *pb.SenderKeyRecordStructure { 117 | p := &pb.SenderKeyRecordStructure{} 118 | for _, sta := range k.senderKeyStates { 119 | p.SenderKeyStates = append(p.SenderKeyStates, sta.structure()) 120 | } 121 | return p 122 | } 123 | -------------------------------------------------------------------------------- /net/Socket.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "net/url" 7 | "sync" 8 | "time" 9 | 10 | "github.com/pkg/errors" 11 | "golang.org/x/net/proxy" 12 | "wa/def" 13 | ) 14 | 15 | //var Sock_Port = "443" 16 | 17 | var Sock_Port = "5222" 18 | 19 | type Socket struct { 20 | net.Conn 21 | Proxy string 22 | Dns map[string]string 23 | 24 | wg sync.WaitGroup 25 | rLock sync.Mutex //deadlock.Mutex 26 | wLock sync.Mutex //deadlock.Mutex 27 | } 28 | 29 | func (this *Socket) Connect() error { 30 | var e error 31 | 32 | var wa_host string = "g.whatsapp.net" 33 | 34 | if this.Dns != nil { 35 | ip, ok := this.Dns[wa_host] 36 | if ok { // find Ip in dns mapping 37 | wa_host = ip 38 | } 39 | } 40 | 41 | if len(this.Proxy) > 0 { 42 | u, e := url.Parse(this.Proxy) 43 | if e != nil { 44 | return errors.Wrap(e, `create socks5 fail`) 45 | } 46 | 47 | dialer, e := proxy.FromURL(u, &net.Dialer{ 48 | Timeout: time.Duration(def.NET_TIMEOUT) * time.Second, 49 | }) 50 | if e != nil { 51 | return errors.Wrap(e, `create socks5 dialer fail`) 52 | } 53 | 54 | this.Conn, e = dialer.Dial("tcp", wa_host+":"+Sock_Port) 55 | 56 | return e 57 | } else { // no proxy 58 | this.Conn, e = net.DialTimeout( 59 | "tcp", 60 | wa_host+":"+Sock_Port, 61 | time.Duration(def.NET_TIMEOUT)*time.Second) 62 | return e 63 | } 64 | } 65 | 66 | func (this *Socket) Close() error { 67 | var e error 68 | 69 | if this.Conn != nil { 70 | e = this.Conn.Close() 71 | } 72 | 73 | this.wg.Wait() 74 | return e 75 | } 76 | 77 | // only return when all bytes are written 78 | func (this *Socket) write_n(data []byte) error { 79 | offset := 0 80 | for offset < len(data) { 81 | nWrite, e := this.Conn.Write(data[offset:]) 82 | if e != nil { 83 | return e 84 | } 85 | offset += nWrite 86 | } 87 | return nil 88 | } 89 | func (this *Socket) EnableReadTimeout(enable bool) { 90 | if enable { 91 | this.Conn.SetReadDeadline(time.Now().Add(time.Duration(def.NET_TIMEOUT) * time.Second)) 92 | } else { 93 | this.Conn.SetReadDeadline(time.Time{}) 94 | } 95 | } 96 | func (this *Socket) EnableWriteTimeout(enable bool) { 97 | if enable { 98 | this.Conn.SetWriteDeadline(time.Now().Add(time.Duration(def.NET_TIMEOUT) * time.Second)) 99 | } else { 100 | this.Conn.SetWriteDeadline(time.Time{}) 101 | } 102 | } 103 | func (this *Socket) WritePacket(data []byte) error { 104 | this.wLock.Lock() 105 | defer this.wLock.Unlock() 106 | 107 | this.wg.Add(1) 108 | defer this.wg.Done() 109 | 110 | if this.Conn == nil { 111 | return errors.New(`socket not connected`) 112 | } 113 | this.EnableWriteTimeout(true) 114 | defer this.EnableWriteTimeout(false) 115 | 116 | buf := make([]byte, 4) 117 | binary.BigEndian.PutUint32(buf[0:], uint32(len(data))) 118 | 119 | pkt := append(buf[1:], data...) // 3-byte-len + data 120 | 121 | return this.write_n(pkt) 122 | } 123 | 124 | func (this *Socket) read_3_byte_len() (int, error) { 125 | bf, e := this.read_n(3) 126 | if e != nil { 127 | return 0, e 128 | } 129 | l := binary.BigEndian.Uint32(append([]byte{0}, bf[:]...)) 130 | 131 | return int(l), nil 132 | } 133 | func (this *Socket) read_n(n int) ([]byte, error) { 134 | left := n 135 | ret := []byte{} 136 | 137 | for left > 0 { 138 | chunk := make([]byte, left) 139 | nRead, e := this.Conn.Read(chunk) 140 | if e != nil { 141 | return nil, e 142 | } 143 | left -= nRead 144 | ret = append(ret, chunk[0:nRead]...) 145 | } 146 | 147 | return ret, nil 148 | } 149 | func (this *Socket) ReadPacket() ([]byte, error) { 150 | this.rLock.Lock() 151 | defer this.rLock.Unlock() 152 | if this.Conn == nil { 153 | return nil, errors.New(`socket not connected`) 154 | } 155 | this.wg.Add(1) 156 | defer this.wg.Done() 157 | 158 | // read 3 byte 159 | body_len, e := this.read_3_byte_len() 160 | if e != nil { 161 | return nil, e 162 | } 163 | 164 | // read body 165 | return this.read_n(body_len) 166 | } 167 | -------------------------------------------------------------------------------- /signal/ratchet/receiver.go: -------------------------------------------------------------------------------- 1 | package ratchet 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/keys/identity" 6 | ) 7 | 8 | // NewReceiverParameters creates a structure with all the keys needed to construct 9 | // a new session when we are receiving a message from a user for the first time. 10 | func NewReceiverParameters(ourIdentityKey *identity.KeyPair, ourSignedPreKey *ecc.ECKeyPair, 11 | ourOneTimePreKey *ecc.ECKeyPair, ourRatchetKey *ecc.ECKeyPair, 12 | theirBaseKey ecc.ECPublicKeyable, theirIdentityKey *identity.Key) *ReceiverParameters { 13 | 14 | receiverParams := ReceiverParameters{ 15 | ourIdentityKeyPair: ourIdentityKey, 16 | ourSignedPreKey: ourSignedPreKey, 17 | ourOneTimePreKey: ourOneTimePreKey, 18 | ourRatchetKey: ourRatchetKey, 19 | theirBaseKey: theirBaseKey, 20 | theirIdentityKey: theirIdentityKey, 21 | } 22 | 23 | return &receiverParams 24 | } 25 | 26 | // NewEmptyReceiverParameters creates an empty structure with the receiver parameters 27 | // needed to create a session. You should use the `set` functions to set all the 28 | // necessary keys needed to build a session. 29 | func NewEmptyReceiverParameters() *ReceiverParameters { 30 | receiverParams := ReceiverParameters{} 31 | 32 | return &receiverParams 33 | } 34 | 35 | // ReceiverParameters describes the session parameters if we are receiving 36 | // a message from someone for the first time. These parameters are used as 37 | // the basis for deriving a shared secret with the sender. 38 | type ReceiverParameters struct { 39 | ourIdentityKeyPair *identity.KeyPair 40 | ourSignedPreKey *ecc.ECKeyPair 41 | ourOneTimePreKey *ecc.ECKeyPair 42 | ourRatchetKey *ecc.ECKeyPair 43 | 44 | theirBaseKey ecc.ECPublicKeyable 45 | theirIdentityKey *identity.Key 46 | } 47 | 48 | // OurIdentityKeyPair returns the identity key of the receiver. 49 | func (r *ReceiverParameters) OurIdentityKeyPair() *identity.KeyPair { 50 | return r.ourIdentityKeyPair 51 | } 52 | 53 | // OurSignedPreKey returns the signed prekey of the receiver. 54 | func (r *ReceiverParameters) OurSignedPreKey() *ecc.ECKeyPair { 55 | return r.ourSignedPreKey 56 | } 57 | 58 | // OurOneTimePreKey returns the one time prekey of the receiver. 59 | func (r *ReceiverParameters) OurOneTimePreKey() *ecc.ECKeyPair { 60 | return r.ourOneTimePreKey 61 | } 62 | 63 | // OurRatchetKey returns the ratchet key of the receiver. 64 | func (r *ReceiverParameters) OurRatchetKey() *ecc.ECKeyPair { 65 | return r.ourRatchetKey 66 | } 67 | 68 | // TheirBaseKey returns the base key of the sender. 69 | func (r *ReceiverParameters) TheirBaseKey() ecc.ECPublicKeyable { 70 | return r.theirBaseKey 71 | } 72 | 73 | // TheirIdentityKey returns the identity key of the sender. 74 | func (r *ReceiverParameters) TheirIdentityKey() *identity.Key { 75 | return r.theirIdentityKey 76 | } 77 | 78 | // SetOurIdentityKeyPair sets the identity key of the receiver. 79 | func (r *ReceiverParameters) SetOurIdentityKeyPair(ourIdentityKey *identity.KeyPair) { 80 | r.ourIdentityKeyPair = ourIdentityKey 81 | } 82 | 83 | // SetOurSignedPreKey sets the signed prekey of the receiver. 84 | func (r *ReceiverParameters) SetOurSignedPreKey(ourSignedPreKey *ecc.ECKeyPair) { 85 | r.ourSignedPreKey = ourSignedPreKey 86 | } 87 | 88 | // SetOurOneTimePreKey sets the one time prekey of the receiver. 89 | func (r *ReceiverParameters) SetOurOneTimePreKey(ourOneTimePreKey *ecc.ECKeyPair) { 90 | r.ourOneTimePreKey = ourOneTimePreKey 91 | } 92 | 93 | // SetOurRatchetKey sets the ratchet key of the receiver. 94 | func (r *ReceiverParameters) SetOurRatchetKey(ourRatchetKey *ecc.ECKeyPair) { 95 | r.ourRatchetKey = ourRatchetKey 96 | } 97 | 98 | // SetTheirBaseKey sets the base key of the sender. 99 | func (r *ReceiverParameters) SetTheirBaseKey(theirBaseKey ecc.ECPublicKeyable) { 100 | r.theirBaseKey = theirBaseKey 101 | } 102 | 103 | // SetTheirIdentityKey sets the identity key of the sender. 104 | func (r *ReceiverParameters) SetTheirIdentityKey(theirIdentityKey *identity.Key) { 105 | r.theirIdentityKey = theirIdentityKey 106 | } 107 | -------------------------------------------------------------------------------- /signal/ratchet/sender.go: -------------------------------------------------------------------------------- 1 | package ratchet 2 | 3 | import ( 4 | "wa/signal/ecc" 5 | "wa/signal/keys/identity" 6 | ) 7 | 8 | // NewSenderParameters creates a structure with all the keys needed to construct 9 | // a new session when we are sending a message to a recipient for the first time. 10 | func NewSenderParameters(ourIdentityKey *identity.KeyPair, ourBaseKey *ecc.ECKeyPair, 11 | theirIdentityKey *identity.Key, theirSignedPreKey ecc.ECPublicKeyable, 12 | theirRatchetKey ecc.ECPublicKeyable, theirOneTimePreKey ecc.ECPublicKeyable) *SenderParameters { 13 | 14 | senderParams := SenderParameters{ 15 | ourIdentityKeyPair: ourIdentityKey, 16 | ourBaseKey: ourBaseKey, 17 | theirIdentityKey: theirIdentityKey, 18 | theirSignedPreKey: theirSignedPreKey, 19 | theirOneTimePreKey: theirOneTimePreKey, 20 | theirRatchetKey: theirRatchetKey, 21 | } 22 | 23 | return &senderParams 24 | } 25 | 26 | // NewEmptySenderParameters creates an empty structure with the sender parameters 27 | // needed to create a session. You should use the `set` functions to set all the 28 | // necessary keys needed to build a session. 29 | func NewEmptySenderParameters() *SenderParameters { 30 | senderParams := SenderParameters{} 31 | 32 | return &senderParams 33 | } 34 | 35 | // SenderParameters describes the session parameters if we are sending the 36 | // recipient a message for the first time. These parameters are used as the 37 | // basis for deriving a shared secret with a recipient. 38 | type SenderParameters struct { 39 | ourIdentityKeyPair *identity.KeyPair 40 | ourBaseKey *ecc.ECKeyPair 41 | 42 | theirIdentityKey *identity.Key 43 | theirSignedPreKey ecc.ECPublicKeyable 44 | theirOneTimePreKey ecc.ECPublicKeyable 45 | theirRatchetKey ecc.ECPublicKeyable 46 | } 47 | 48 | // OurIdentityKey returns the identity key pair of the sender. 49 | func (s *SenderParameters) OurIdentityKey() *identity.KeyPair { 50 | return s.ourIdentityKeyPair 51 | } 52 | 53 | // OurBaseKey returns the base ECC key pair of the sender. 54 | func (s *SenderParameters) OurBaseKey() *ecc.ECKeyPair { 55 | return s.ourBaseKey 56 | } 57 | 58 | // TheirIdentityKey returns the identity public key of the receiver. 59 | func (s *SenderParameters) TheirIdentityKey() *identity.Key { 60 | return s.theirIdentityKey 61 | } 62 | 63 | // TheirSignedPreKey returns the signed pre key of the receiver. 64 | func (s *SenderParameters) TheirSignedPreKey() ecc.ECPublicKeyable { 65 | return s.theirSignedPreKey 66 | } 67 | 68 | // TheirOneTimePreKey returns the receiver's one time prekey. 69 | func (s *SenderParameters) TheirOneTimePreKey() ecc.ECPublicKeyable { 70 | return s.theirOneTimePreKey 71 | } 72 | 73 | // TheirRatchetKey returns the receiver's ratchet key. 74 | func (s *SenderParameters) TheirRatchetKey() ecc.ECPublicKeyable { 75 | return s.theirRatchetKey 76 | } 77 | 78 | // SetOurIdentityKey sets the identity key pair of the sender. 79 | func (s *SenderParameters) SetOurIdentityKey(ourIdentityKey *identity.KeyPair) { 80 | s.ourIdentityKeyPair = ourIdentityKey 81 | } 82 | 83 | // SetOurBaseKey sets the base ECC key pair of the sender. 84 | func (s *SenderParameters) SetOurBaseKey(ourBaseKey *ecc.ECKeyPair) { 85 | s.ourBaseKey = ourBaseKey 86 | } 87 | 88 | // SetTheirIdentityKey sets the identity public key of the receiver. 89 | func (s *SenderParameters) SetTheirIdentityKey(theirIdentityKey *identity.Key) { 90 | s.theirIdentityKey = theirIdentityKey 91 | } 92 | 93 | // SetTheirSignedPreKey sets the signed pre key of the receiver. 94 | func (s *SenderParameters) SetTheirSignedPreKey(theirSignedPreKey ecc.ECPublicKeyable) { 95 | s.theirSignedPreKey = theirSignedPreKey 96 | } 97 | 98 | // SetTheirOneTimePreKey sets the receiver's one time prekey. 99 | func (s *SenderParameters) SetTheirOneTimePreKey(theirOneTimePreKey ecc.ECPublicKeyable) { 100 | s.theirOneTimePreKey = theirOneTimePreKey 101 | } 102 | 103 | // SetTheirRatchetKey sets the receiver's ratchet key. 104 | func (s *SenderParameters) SetTheirRatchetKey(theirRatchetKey ecc.ECPublicKeyable) { 105 | s.theirRatchetKey = theirRatchetKey 106 | } 107 | -------------------------------------------------------------------------------- /signal/keys/chain/key.go: -------------------------------------------------------------------------------- 1 | // Package chain provides chain keys used in double ratchet sessions. 2 | package chain 3 | 4 | import ( 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | 8 | "wa/signal/kdf" 9 | "wa/signal/keys/message" 10 | "wa/signal/pb" 11 | 12 | "google.golang.org/protobuf/proto" 13 | ) 14 | 15 | var messageKeySeed = []byte{0x01} 16 | var chainKeySeed = []byte{0x02} 17 | 18 | // NewKey returns a new chain key with the given kdf, key, and index 19 | func NewKey(kdf kdf.HKDF, key []byte, index uint32) *Key { 20 | chainKey := Key{ 21 | kdf: kdf, 22 | key: key, 23 | index: index, 24 | } 25 | 26 | return &chainKey 27 | } 28 | 29 | // NewKeyFromStruct will return a chain key built from the given structure. 30 | func NewKeyFromStruct(structure *pb.SessionStructure_Chain_ChainKey, kdf kdf.HKDF) *Key { 31 | return NewKey( 32 | kdf, 33 | structure.Key, 34 | structure.GetIndex(), 35 | ) 36 | } 37 | 38 | // NewStructFromKey will return a chain key structure for serialization. 39 | func NewStructFromKey(key *Key) *pb.SessionStructure_Chain_ChainKey { 40 | return &pb.SessionStructure_Chain_ChainKey{ 41 | Key: key.key, 42 | Index: proto.Uint32(key.index), 43 | } 44 | } 45 | 46 | // Key is used for generating message keys. This key "ratchets" every time a 47 | // message key is generated. Every time the chain key ratchets forward, its index 48 | // increases by one. 49 | type Key struct { 50 | kdf kdf.HKDF 51 | key []byte 52 | index uint32 // Index's maximum size: 4,294,967,295 53 | } 54 | 55 | // Current returns the current ChainKey struct. 56 | func (c *Key) Current() *Key { 57 | return c 58 | } 59 | 60 | // Key returns the ChainKey's key material. 61 | func (c *Key) Key() []byte { 62 | return c.key 63 | } 64 | 65 | // SetKey will set the ChainKey's key material. 66 | func (c *Key) SetKey(key []byte) { 67 | c.key = key 68 | } 69 | 70 | // Index returns how many times the ChainKey has been "ratcheted" forward. 71 | func (c *Key) Index() uint32 { 72 | return c.index 73 | } 74 | 75 | // SetIndex sets how many times the ChainKey has been "ratcheted" forward. 76 | func (c *Key) SetIndex(index uint32) { 77 | c.index = index 78 | } 79 | 80 | // NextKey uses the key derivation function to generate a new ChainKey. 81 | func (c *Key) NextKey() *Key { 82 | nextKey := c.BaseMaterial(chainKeySeed) 83 | return NewKey(c.kdf, nextKey, c.index+1) 84 | } 85 | 86 | // MessageKeys returns message keys, which includes the cipherkey, mac, iv, and index. 87 | func (c *Key) MessageKeys() *message.Keys { 88 | inputKeyMaterial := c.BaseMaterial(messageKeySeed) 89 | keyMaterialBytes, _ := c.kdf(inputKeyMaterial, nil, []byte(message.KdfSalt), message.DerivedSecretsSize) 90 | keyMaterial := newKeyMaterial(keyMaterialBytes) 91 | 92 | // Use the key material returned from the key derivation function for our cipherkey, mac, and iv. 93 | messageKeys := message.NewKeys( 94 | keyMaterial.CipherKey, // Use the first 32 bytes of the key material for the CipherKey 95 | keyMaterial.MacKey, // Use bytes 32-64 of the key material for the MacKey 96 | keyMaterial.IV, // Use the last 16 bytes for the IV. 97 | c.Index(), // Attach the chain key's index to the message keys. 98 | ) 99 | 100 | return messageKeys 101 | } 102 | 103 | // BaseMaterial uses hmac to derive the base material used in the key derivation function for a new key. 104 | func (c *Key) BaseMaterial(seed []byte) []byte { 105 | mac := hmac.New(sha256.New, c.key[:]) 106 | mac.Write(seed) 107 | 108 | return mac.Sum(nil) 109 | } 110 | 111 | // NewKeyMaterial takes an 80-byte slice derived from a key derivation function and splits 112 | // it into the cipherkey, mac, and iv. 113 | func newKeyMaterial(keyMaterialBytes []byte) *kdf.KeyMaterial { 114 | cipherKey := keyMaterialBytes[:32] // Use the first 32 bytes of the key material for the CipherKey 115 | macKey := keyMaterialBytes[32:64] // Use bytes 32-64 of the key material for the MacKey 116 | iv := keyMaterialBytes[64:80] // Use the last 16 bytes for the IV. 117 | 118 | keyMaterial := kdf.KeyMaterial{ 119 | CipherKey: cipherKey, 120 | MacKey: macKey, 121 | IV: iv, 122 | } 123 | 124 | return &keyMaterial 125 | } 126 | -------------------------------------------------------------------------------- /tools/cloner/cloner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "flag" 8 | 9 | "afs" 10 | "ajson" 11 | "algo" 12 | "github.com/beevik/etree" 13 | "gorm.io/driver/sqlite" 14 | "gorm.io/gorm" 15 | "wa/def" 16 | "wa/def/clone" 17 | ) 18 | 19 | var dir string 20 | var out string 21 | 22 | func init() { 23 | flag.StringVar(&dir, "dir", "./com.whatsapp", "") 24 | flag.StringVar(&out, "out", "./clone.json", "") 25 | } 26 | 27 | func main() { 28 | 29 | flag.Parse() 30 | 31 | gene := &clone.Gene{} 32 | 33 | { 34 | doc := etree.NewDocument() 35 | if e := doc.ReadFromFile(dir + "/shared_prefs/keystore.xml"); e == nil { 36 | client_static_keypair_pwd_enc := doc.FindElements("//map/string[@name='client_static_keypair_pwd_enc']")[0].Text() 37 | j, e := ajson.Parse(client_static_keypair_pwd_enc) 38 | if e != nil { 39 | panic(e) 40 | } 41 | 42 | staticI_enc, e1 := algo.B64PadDec(j.GetIndex(1).String()) 43 | 44 | iv, e2 := algo.B64PadDec(j.GetIndex(2).String()) 45 | 46 | salt, e3 := algo.B64PadDec(j.GetIndex(3).String()) 47 | 48 | password := j.GetIndex(4).String() 49 | 50 | if e1 != nil || e2 != nil || e3 != nil { 51 | panic(errors.New(`fail B64PadDec`)) 52 | } 53 | key := algo.PbkdfSha1( 54 | append(def.RC2_FIXED_25, []byte(password)...), 55 | salt, 16, 16, 56 | ) 57 | 58 | bs, e := algo.AesOfbDecrypt(staticI_enc, key, iv, &algo.None{}) 59 | if e != nil { 60 | panic(e) 61 | } 62 | gene.StaticPriv = bs[0:0x20] 63 | gene.StaticPub = bs[0x20:] 64 | } 65 | 66 | } 67 | { 68 | doc := etree.NewDocument() 69 | if e := doc.ReadFromFile(dir + "/shared_prefs/com.whatsapp_preferences_light.xml"); e == nil { 70 | x := doc.FindElements("//map/string[@name='push_name']") 71 | if len(x) > 0 { 72 | gene.Nick = x[0].Text() 73 | } 74 | 75 | gene.Fdid = doc.FindElements("//map/string[@name='phoneid_id']")[0].Text() 76 | 77 | z := doc.FindElements("//map/string[@name='routing_info']") 78 | if len(z) > 0 { 79 | ri := z[0].Text() 80 | var e error 81 | gene.RoutingInfo, e = base64.RawURLEncoding.DecodeString(ri) 82 | if e != nil { 83 | panic(e) 84 | } 85 | } 86 | 87 | y := doc.FindElements("//map/string[@name='last_datacenter']") 88 | if len(y) > 0 { 89 | gene.NoiseLocation = y[0].Text() 90 | } 91 | } 92 | } 93 | { 94 | doc := etree.NewDocument() 95 | if e := doc.ReadFromFile(dir + "/shared_prefs/ab-props.xml"); e == nil { 96 | x := doc.FindElements("//map/string[@name='ab_props:sys:config_key']") 97 | if len(x) > 0 { 98 | gene.AbPropsConfigKey = x[0].Text() 99 | } 100 | 101 | y := doc.FindElements("//map/string[@name='ab_props:sys:config_hash']") 102 | if len(y) > 0 { 103 | gene.AbPropsConfigHash = y[0].Text() 104 | } 105 | } 106 | } 107 | { 108 | doc := etree.NewDocument() 109 | if e := doc.ReadFromFile(dir + "/shared_prefs/com.whatsapp_preferences.xml"); e == nil { 110 | x := doc.FindElements("//map/string[@name='server_props:config_key']") 111 | if len(x) > 0 { 112 | gene.ServerPropsConfigKey = x[0].Text() 113 | } 114 | 115 | y := doc.FindElements("//map/string[@name='server_props:config_hash']") 116 | if len(y) > 0 { 117 | gene.ServerPropsConfigHash = y[0].Text() 118 | } 119 | } 120 | } 121 | db, e := gorm.Open(sqlite.Open(dir+"/databases/axolotl.db"), &gorm.Config{}) 122 | if e != nil { 123 | panic(e) 124 | } 125 | { 126 | { 127 | iden := &clone.Identity{RecipientId: -1} 128 | if e = db.First(iden).Error; e != nil { 129 | panic(e) 130 | } 131 | gene.Identity = *iden 132 | // djb key 133 | if len(gene.Identity.PublicKey) == 33 && gene.Identity.PublicKey[0] == 5 { 134 | gene.Identity.PublicKey = gene.Identity.PublicKey[1:] 135 | } 136 | } 137 | { 138 | prekeys := []clone.Prekey{} 139 | if e = db.Find(&prekeys).Error; e != nil { 140 | panic(e) 141 | } 142 | gene.Prekeys = prekeys 143 | } 144 | } 145 | { 146 | { 147 | spk := &clone.SignedPrekey{} 148 | if e = db.First(spk).Error; e != nil { 149 | panic(e) 150 | } 151 | gene.SignedPrekey = *spk 152 | } 153 | } 154 | 155 | bs, _ := json.MarshalIndent(gene, "", " ") 156 | afs.Write(out, bs) 157 | } 158 | -------------------------------------------------------------------------------- /core/wam_message.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "arand" 8 | "i" 9 | "wa/pb" 10 | "wa/wam" 11 | ) 12 | 13 | func wam_media_type(media_t pb.Media_Type) int32 { 14 | var t int32 = 1 15 | switch media_t { 16 | case pb.Media_Text: 17 | t = 1 18 | case pb.Media_Image: 19 | t = 2 20 | case pb.Media_Contact: 21 | t = 7 22 | case pb.Media_Url: 23 | t = 9 24 | case pb.Media_Document: 25 | t = 8 26 | case pb.Media_Ptt: 27 | t = 5 28 | case pb.Media_Video: 29 | t = 3 30 | case pb.Media_Contact_Array: 31 | t = 13 32 | case pb.Media_Sticker: 33 | t = 16 34 | } 35 | return t 36 | } 37 | 38 | func (a *Acc) wam_message_send( 39 | media_t pb.Media_Type, 40 | send_begin time.Time, 41 | msg_type int, 42 | international bool, 43 | ) error { 44 | return a.AddWamEventBuf(wam.WamMessageSend, func(cc *wam.ClassChunk) { 45 | cc.Append(25, 1) // deviceSizeBucket 46 | cc.Append(23, 0) // e2eBackfill 47 | cc.Append(21, 0) // ephemeralityDuration 48 | cc.Append(22, 0) // isViewOnce 49 | 50 | if media_t == pb.Media_Image { 51 | cc.Append(8, 0) // mediaCaptionPresent 52 | } 53 | cc.Append(4, 0) // messageIsForward 54 | cc.Append(7, i.F(international, 1, 0)) // messageIsInternational 55 | cc.Append(3, wam_media_type(media_t)) // messageMediaType 56 | cc.Append(1, 1) // messageSendResult 57 | cc.Append(17, 0) // messageSendResultIsTerminal 58 | 59 | cc.Append(11, time.Since(send_begin).Milliseconds()) // messageSendT 60 | 61 | cc.Append(2, msg_type) // messageType 62 | 63 | if media_t == pb.Media_Sticker { 64 | cc.Append(18, 1) // stickerIsFirstParty 65 | } 66 | if media_t == pb.Media_Image { // image 67 | cc.Append(20, arand.Int(500, 1200)) // thumbSize 68 | } 69 | }) 70 | } 71 | 72 | func (a *Acc) wam_e2e_message_send( 73 | media_t pb.Media_Type, 74 | e2eCiphertextType, e2eCiphertextVersion, e2eDestination int32, 75 | ) error { 76 | return a.AddWamEventBuf(wam.WamE2eMessageSend, func(cc *wam.ClassChunk) { 77 | cc.Append(5, e2eCiphertextType) 78 | cc.Append(6, e2eCiphertextVersion) 79 | cc.Append(4, e2eDestination) 80 | //cc.Append(2) // e2eFailureReason 81 | cc.Append(8, 1) // e2eReceiverType 82 | cc.Append(1, 1) // e2eSuccessful 83 | cc.Append(9, 0) // encRetryCount 84 | if e2eDestination != 3 { // not sns 85 | cc.Append(7, wam_media_type(media_t)) // messageMediaType 86 | } 87 | cc.Append(3, 0) // retryCount 88 | }) 89 | } 90 | 91 | func (a *Acc) wam_e2e_message_recv( 92 | media_t pb.Media_Type, 93 | timestamp int, 94 | e2eCiphertextType, e2eCiphertextVersion, 95 | e2eDestination int32, 96 | ) error { 97 | return a.AddWamEventBuf(wam.WamE2eMessageRecv, func(cc *wam.ClassChunk) { 98 | cc.Append(5, e2eCiphertextType) 99 | cc.Append(6, e2eCiphertextVersion) 100 | cc.Append(4, e2eDestination) 101 | cc.Append(8, 2) // e2eSenderType always 2 102 | cc.Append(1, 1) // e2eSuccessful 103 | 104 | cc.Append(7, wam_media_type(media_t)) // messageMediaType 105 | { 106 | is_offline := time.Now().Unix()-int64(timestamp) > 60 107 | cc.Append(9, i.F(is_offline, 1, 0)) // offline 108 | } 109 | cc.Append(3, 0) // retryCount 110 | }) 111 | } 112 | func (a *Acc) wam_message_receive( 113 | media_t pb.Media_Type, 114 | timestamp int, // time > 1min == offline 115 | msg_type int, // 1:personal 2:group 3:broadcast 4:sns 116 | international bool, 117 | ) error { 118 | return a.AddWamEventBuf(wam.WamMessageReceive, func(cc *wam.ClassChunk) { 119 | cc.Append(9, 0) // isViewOnce 120 | cc.Append(4, i.F(international, 1, 0)) // messageIsInternational 121 | { 122 | is_offline := time.Now().Unix()-int64(timestamp) > 60 123 | cc.Append(5, i.F(is_offline, 1, 0)) // messageIsOffline 124 | } 125 | cc.Append(2, wam_media_type(media_t)) // messageMediaType 126 | cc.Append(6, arand.Int(-300, 300)) // messageReceiveT0 127 | cc.Append(7, arand.Int(15, 70)) // messageReceiveT1 128 | cc.Append(1, msg_type) // messageType 129 | }) 130 | } 131 | 132 | func wam_cipher_text_type(type_ string) int32 { 133 | switch type_ { 134 | case "msg": 135 | return 0 136 | case "pkmsg": 137 | return 1 138 | case "skmsg": 139 | return 2 140 | } 141 | return 0 142 | } 143 | func wam_cipher_text_ver(ver string) int32 { 144 | v, e := strconv.Atoi(ver) 145 | if e == nil { 146 | return int32(v) 147 | } 148 | return 2 149 | } 150 | -------------------------------------------------------------------------------- /noise/patterns.go: -------------------------------------------------------------------------------- 1 | package noise 2 | 3 | var HandshakeNN = HandshakePattern{ 4 | Name: "NN", 5 | Messages: [][]MessagePattern{ 6 | {MessagePatternE}, 7 | {MessagePatternE, MessagePatternDHEE}, 8 | }, 9 | } 10 | 11 | var HandshakeKN = HandshakePattern{ 12 | Name: "KN", 13 | InitiatorPreMessages: []MessagePattern{MessagePatternS}, 14 | Messages: [][]MessagePattern{ 15 | {MessagePatternE}, 16 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE}, 17 | }, 18 | } 19 | 20 | var HandshakeNK = HandshakePattern{ 21 | Name: "NK", 22 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 23 | Messages: [][]MessagePattern{ 24 | {MessagePatternE, MessagePatternDHES}, 25 | {MessagePatternE, MessagePatternDHEE}, 26 | }, 27 | } 28 | 29 | var HandshakeKK = HandshakePattern{ 30 | Name: "KK", 31 | InitiatorPreMessages: []MessagePattern{MessagePatternS}, 32 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 33 | Messages: [][]MessagePattern{ 34 | {MessagePatternE, MessagePatternDHES, MessagePatternDHSS}, 35 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE}, 36 | }, 37 | } 38 | 39 | var HandshakeNX = HandshakePattern{ 40 | Name: "NX", 41 | Messages: [][]MessagePattern{ 42 | {MessagePatternE}, 43 | {MessagePatternE, MessagePatternDHEE, MessagePatternS, MessagePatternDHES}, 44 | }, 45 | } 46 | 47 | var HandshakeKX = HandshakePattern{ 48 | Name: "KX", 49 | InitiatorPreMessages: []MessagePattern{MessagePatternS}, 50 | Messages: [][]MessagePattern{ 51 | {MessagePatternE}, 52 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE, MessagePatternS, MessagePatternDHES}, 53 | }, 54 | } 55 | 56 | var HandshakeXN = HandshakePattern{ 57 | Name: "XN", 58 | Messages: [][]MessagePattern{ 59 | {MessagePatternE}, 60 | {MessagePatternE, MessagePatternDHEE}, 61 | {MessagePatternS, MessagePatternDHSE}, 62 | }, 63 | } 64 | 65 | var HandshakeIN = HandshakePattern{ 66 | Name: "IN", 67 | Messages: [][]MessagePattern{ 68 | {MessagePatternE, MessagePatternS}, 69 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE}, 70 | }, 71 | } 72 | 73 | var HandshakeXK = HandshakePattern{ 74 | Name: "XK", 75 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 76 | Messages: [][]MessagePattern{ 77 | {MessagePatternE, MessagePatternDHES}, 78 | {MessagePatternE, MessagePatternDHEE}, 79 | {MessagePatternS, MessagePatternDHSE}, 80 | }, 81 | } 82 | 83 | var HandshakeIK = HandshakePattern{ 84 | Name: "IK", 85 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 86 | Messages: [][]MessagePattern{ 87 | {MessagePatternE, MessagePatternDHES, MessagePatternS, MessagePatternDHSS}, 88 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE}, 89 | }, 90 | } 91 | 92 | var HandshakeXX = HandshakePattern{ 93 | Name: "XX", 94 | Messages: [][]MessagePattern{ 95 | {MessagePatternE}, 96 | {MessagePatternE, MessagePatternDHEE, MessagePatternS, MessagePatternDHES}, 97 | {MessagePatternS, MessagePatternDHSE}, 98 | }, 99 | } 100 | 101 | var HandshakeXXfallback = HandshakePattern{ 102 | Name: "XXfallback", 103 | ResponderPreMessages: []MessagePattern{MessagePatternE}, 104 | Messages: [][]MessagePattern{ 105 | {MessagePatternE, MessagePatternDHEE, MessagePatternS, MessagePatternDHSE}, 106 | {MessagePatternS, MessagePatternDHES}, 107 | }, 108 | } 109 | 110 | var HandshakeIX = HandshakePattern{ 111 | Name: "IX", 112 | Messages: [][]MessagePattern{ 113 | {MessagePatternE, MessagePatternS}, 114 | {MessagePatternE, MessagePatternDHEE, MessagePatternDHSE, MessagePatternS, MessagePatternDHES}, 115 | }, 116 | } 117 | 118 | var HandshakeN = HandshakePattern{ 119 | Name: "N", 120 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 121 | Messages: [][]MessagePattern{ 122 | {MessagePatternE, MessagePatternDHES}, 123 | }, 124 | } 125 | 126 | var HandshakeK = HandshakePattern{ 127 | Name: "K", 128 | InitiatorPreMessages: []MessagePattern{MessagePatternS}, 129 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 130 | Messages: [][]MessagePattern{ 131 | {MessagePatternE, MessagePatternDHES, MessagePatternDHSS}, 132 | }, 133 | } 134 | 135 | var HandshakeX = HandshakePattern{ 136 | Name: "X", 137 | ResponderPreMessages: []MessagePattern{MessagePatternS}, 138 | Messages: [][]MessagePattern{ 139 | {MessagePatternE, MessagePatternDHES, MessagePatternS, MessagePatternDHSS}, 140 | }, 141 | } 142 | -------------------------------------------------------------------------------- /xmpp/node.go: -------------------------------------------------------------------------------- 1 | package xmpp 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "strings" 7 | 8 | "ahex" 9 | "ajson" 10 | 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type KeyValue struct { 15 | Key string 16 | Value string 17 | Type int 18 | } 19 | 20 | func (kv *KeyValue) IsCompliant() bool { 21 | return kv.Type == 1 || 22 | strings.Contains(kv.Value, "s.whatsapp.net") || 23 | strings.Contains(kv.Value, "g.us") 24 | } 25 | 26 | type Node struct { 27 | Compressed bool 28 | 29 | Tag string 30 | Attrs []*KeyValue 31 | Data []byte 32 | Children []*Node 33 | } 34 | 35 | const INDENT = ` ` 36 | 37 | func (n1 *Node) Compare(n2 *Node) bool { 38 | if n1.Tag != n2.Tag { 39 | return false 40 | } 41 | if !bytes.Equal(n1.Data, n2.Data) { 42 | return false 43 | } 44 | if !reflect.DeepEqual(n1.Attrs, n2.Attrs) { 45 | return false 46 | } 47 | if !reflect.DeepEqual(n1.Children, n2.Children) { 48 | return false 49 | } 50 | return true 51 | } 52 | 53 | func (n *Node) ToString() string { 54 | return n.ToJson().ToStringIndent() 55 | } 56 | func (n *Node) ToJson() *ajson.Json { 57 | ret := ajson.New() 58 | 59 | ret.Set("Tag", n.Tag) 60 | if len(n.Attrs) > 0 { 61 | attr := ajson.New() 62 | for _, a := range n.Attrs { 63 | attr.Set(a.Key, a.Value) 64 | } 65 | ret.Set(`Attrs`, attr) 66 | } 67 | if n.Data != nil { 68 | ret.Set("Data", ahex.Enc(n.Data)) 69 | } 70 | if len(n.Children) > 0 { 71 | children := []interface{}{} 72 | 73 | for _, c := range n.Children { 74 | children = append(children, c.ToJson().Data()) 75 | } 76 | ret.Set("Children", children) 77 | } 78 | return ret 79 | } 80 | func ParseJson(j *ajson.Json) (*Node, error) { 81 | n := &Node{} 82 | 83 | // Tag 84 | tag, e := j.Get(`Tag`).TryString() 85 | if e != nil { 86 | return nil, errors.New(`fail parse Tag`) 87 | } 88 | n.Tag = tag 89 | 90 | // Attrs 91 | if j.Exists(`Attrs`) { 92 | attrs, ok := j.TryGet(`Attrs`) 93 | if !ok { 94 | return nil, errors.New(`invalid "Attrs"`) 95 | } 96 | m, e := attrs.TryMap() 97 | if e != nil { 98 | return nil, errors.New(`fail parse "Attrs" to map`) 99 | } 100 | for k, v := range m { 101 | vs, ok := v.(string) 102 | if !ok { 103 | return nil, errors.New(`fail cast Attr "` + k + `" to string`) 104 | } 105 | n.Attrs = append(n.Attrs, &KeyValue{Key: k, Value: vs}) 106 | } 107 | } 108 | 109 | // Data 110 | if j.Exists(`Data`) { 111 | data, e := j.Get(`Data`).TryString() 112 | if e != nil { 113 | return nil, e 114 | } 115 | n.Data = ahex.Dec(data) 116 | } 117 | 118 | // Children 119 | if j.Exists(`Children`) { 120 | chs, e := j.Get(`Children`).TryJsonArray() 121 | if e != nil { 122 | return nil, e 123 | } 124 | for _, ch := range chs { 125 | nn, e := ParseJson(ch) 126 | if e != nil { 127 | return nil, e 128 | } 129 | n.Children = append(n.Children, nn) 130 | } 131 | } 132 | 133 | return n, nil 134 | } 135 | 136 | func (n *Node) GetAttr(attr string) (string, bool) { 137 | for _, a := range n.Attrs { 138 | if a.Key == attr { 139 | return a.Value, true 140 | } 141 | } 142 | return ``, false 143 | } 144 | func (n *Node) SetAttr(attr, value string) { 145 | for _, a := range n.Attrs { 146 | if a.Key == attr { 147 | a.Value = value 148 | return 149 | } 150 | } 151 | // if not found, add one 152 | n.Attrs = append(n.Attrs, &KeyValue{ 153 | Key: attr, Value: value, 154 | }) 155 | } 156 | func (n *Node) MapAttrs() map[string]string { 157 | m := map[string]string{} 158 | for _, attr := range n.Attrs { 159 | m[attr.Key] = attr.Value 160 | } 161 | return m 162 | } 163 | func (n *Node) GetAttrs(keys []string) ([]string, error) { 164 | m := n.MapAttrs() 165 | 166 | ret := []string{} 167 | for _, key := range keys { 168 | val, ok := m[key] 169 | if !ok { 170 | return nil, errors.New(`missing ` + key) 171 | } 172 | ret = append(ret, val) 173 | } 174 | return ret, nil 175 | } 176 | 177 | func (n *Node) FindChild(fn func(*Node) bool) (*Node, bool) { 178 | for _, ch := range n.Children { 179 | if fn(ch) { 180 | return ch, true 181 | } 182 | } 183 | return nil, false 184 | } 185 | func (n *Node) FindChildByTag(tag string) (*Node, bool) { 186 | return n.FindChild(func(ch *Node) bool { 187 | return ch.Tag == tag 188 | }) 189 | } 190 | func (n *Node) FindChildWithAttr(key, val string) (*Node, bool) { 191 | return n.FindChild(func(ch *Node) bool { 192 | attr, ok := ch.GetAttr(key) 193 | return ok && attr == val 194 | }) 195 | } 196 | -------------------------------------------------------------------------------- /tools/upgrade/upgrade.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "flag" 7 | "os" 8 | "strings" 9 | 10 | "afs" 11 | "ahex" 12 | "algo" 13 | "aregex" 14 | "run" 15 | "wa/def" 16 | 17 | "github.com/fatih/color" 18 | ) 19 | 20 | func check_change(tag string, prev, curr []byte) { 21 | if bytes.Equal(prev, curr) { 22 | color.HiGreen("%s: same", tag) 23 | } else { 24 | color.HiYellow("%s: changed", tag) 25 | color.HiMagenta("prev:\n" + hex.Dump(prev)) 26 | color.HiBlue("curr:\n" + hex.Dump(curr)) 27 | } 28 | } 29 | 30 | var biz bool 31 | 32 | func init() { 33 | flag.BoolVar(&biz, "biz", false, "") 34 | } 35 | 36 | var DIR string = `/root/Downloads/up/personal` 37 | var __pkg_name__ = `com.whatsapp` 38 | var __apk_fn__ = `wa` 39 | 40 | func main() { 41 | flag.Parse() 42 | 43 | if biz { 44 | __pkg_name__ = `com.whatsapp.w4b` 45 | __apk_fn__ = `wa_biz` 46 | DIR = `/root/Downloads/up/biz` 47 | } 48 | 49 | os.Chdir(DIR) 50 | if !afs.Exist(__pkg_name__) { 51 | color.HiRed("no dir: %s/%s", DIR, __pkg_name__) 52 | color.HiYellow("pull from '/data/data/%s' first", __pkg_name__) 53 | os.Exit(1) 54 | } 55 | if !afs.Exist(__apk_fn__) { 56 | color.HiRed("no dir: %s/%s", DIR, __apk_fn__) 57 | color.HiYellow("call 'apktool d " + __apk_fn__ + "' first") 58 | os.Exit(1) 59 | } 60 | 61 | { // VERSION 62 | out, err, ec := run.RunCommand(DIR, `aapt`, `d`, `badging`, __apk_fn__+`.apk`) 63 | if ec != 0 { 64 | panic(`fail ag: ` + err) 65 | } 66 | s := aregex.Search(out, "versionName='(.+?)'") 67 | color.HiYellow(`VERSION: %s`, s) 68 | } 69 | { // CLASSES_MD5 70 | if !afs.Exist(`classes.dex`) { 71 | color.HiRed("no classes.dex") 72 | os.Exit(1) 73 | } 74 | bs := afs.Read(`classes.dex`) 75 | color.HiYellow(`CLASSES_MD5: %s`, algo.Md5Str(bs)) 76 | } 77 | { // SVR_PUB 78 | bs := afs.Read(__pkg_name__ + `/files/decompressed/libs.spk.zst/libwhatsapp.so`) 79 | p := bytes.Index(bs, def.SVR_PUB) 80 | if p <= 0 { 81 | color.HiYellow("%s: changed", "SVR_PUB") 82 | os.Exit(1) 83 | } else { 84 | color.HiGreen("%s: same", "SVR_PUB") 85 | } 86 | } 87 | { // SALT 88 | // 1. find file contains string `/res/drawable....` 89 | out, err, ec := run.RunCommand(DIR, `ag`, `-l`, `--no-break`, `/res/drawable-hdpi/about_logo.png`, __apk_fn__) 90 | if ec != 0 { 91 | panic(`fail ag: ` + err) 92 | } 93 | fn := strings.Trim(out, "\n") 94 | // 2. regex search 95 | s := aregex.Search(afs.ReadStr(fn), "const-string v1, \"([^\"]+?)\"") 96 | curr, _ := algo.B64Dec(s) 97 | check_change(`SALT`, def.SALT, curr) 98 | } 99 | { // ABOUT_LOGO 100 | curr := afs.Read(__apk_fn__ + `/res/drawable-hdpi/about_logo.png`) 101 | check_change(`ABOUT_LOGO`, def.ABOUT_LOGO(biz), curr) 102 | } 103 | { // SignatureHash 104 | out, err, ec := run.RunCommand(DIR, `keytool`, `-printcert`, `-jarfile`, __apk_fn__+`.apk`) 105 | if ec != 0 { 106 | panic(`fail ag: ` + err) 107 | } 108 | // 2. regex search 109 | s := aregex.Search(out, "SHA256: (.+?)\n") 110 | s = strings.ReplaceAll(s, ":", "") 111 | curr := ahex.Dec(s) 112 | 113 | prev, _ := algo.B64Dec(def.SignatureHash) 114 | check_change(`SignatureHash`, prev, curr) 115 | } 116 | { // SIGNATURE 117 | fn := __apk_fn__ + `/original/META-INF/WHATSAPP.DSA` 118 | out, err, ec := run.RunCommand(DIR, `openssl`, `pkcs7`, `-inform`, `DER`, `-in`, fn, `-print_certs`, `-text`) 119 | if ec != 0 { 120 | panic(`fail ag: ` + err) 121 | } 122 | s := aregex.Search(out, "-----BEGIN CERTIFICATE-----([^-]+?)-----END CERTIFICATE-----") 123 | s = strings.ReplaceAll(s, "\r", "") 124 | s = strings.ReplaceAll(s, "\n", "") 125 | s = strings.ReplaceAll(s, "\t", "") 126 | s = strings.ReplaceAll(s, " ", "") 127 | curr, _ := algo.B64Dec(s) 128 | check_change(`SIGNATURE`, def.SIGNATURE, curr) 129 | } 130 | { // RC2_FIXED_25 131 | // 1. find file contains string 132 | out, err, ec := run.RunCommand(DIR, `ag`, `-l`, `--no-break`, "-Q", `A\u0004\u001d@\u0011\u0018V\u0091\u0002\u0090\u0088\u009f\u009eT(3{;ES`, __apk_fn__) 133 | if ec != 0 { 134 | panic(`fail ag: ` + err) 135 | } 136 | if strings.Contains(out, `.smali`) { 137 | check_change(`RC2_FIXED_25`, []byte{}, []byte{}) 138 | } else { 139 | check_change(`RC2_FIXED_25`, []byte{}, []byte(`str not found`)) 140 | } 141 | } 142 | { 143 | //TODO 144 | color.Blue("Now manually verify WA_41 ED_..") 145 | color.Blue("Now manually verify dict_0/1/2 in xmpp/def.go") 146 | color.Blue("Now manually verify WAM.header [57 41 4d 05] in wam/wam.go") 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /wam/record.go: -------------------------------------------------------------------------------- 1 | package wam 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Record.ClassType 9 | /* 10 | const ( 11 | ClassType_Wild = 0 12 | ClassType_Begin = 1 13 | ClassType_Data = 2 14 | ) 15 | */ 16 | 17 | type Record struct { 18 | ClassType uint8 19 | Id int32 20 | Value interface{} 21 | 22 | IsClassEnd bool // id | 4 23 | } 24 | 25 | func (rec *Record) ToBytes() ([]byte, error) { 26 | w := ByteBuffer{} 27 | first := 0 28 | w.put_byte(0) // placeholder for id-type 29 | 30 | id_len := w.put_u32(uint32(rec.Id)) 31 | if id_len > 2 { // more than 2 bytes 32 | return nil, errors.New(`id too big to fit in 2 bytes`) 33 | } 34 | id_is_two_bytes := uint8(0) 35 | if id_len == 2 { 36 | id_is_two_bytes = 1 37 | } 38 | 39 | var val_type uint8 40 | if rec.Value == nil { 41 | val_type = 0 42 | } else if v, ok := rec.Value.(bool); ok { 43 | if v { 44 | val_type = w.put_number(1) 45 | } else { 46 | val_type = w.put_number(0) 47 | } 48 | 49 | } else if v, ok := rec.Value.(int); ok { 50 | val_type = w.put_number(v) 51 | } else if v, ok := rec.Value.(int64); ok { 52 | val_type = w.put_number(int(v)) 53 | } else if v, ok := rec.Value.(int32); ok { 54 | val_type = w.put_number(int(v)) 55 | } else if v, ok := rec.Value.(float64); ok { 56 | v2 := int(v) 57 | if float64(v2) == v { 58 | val_type = w.put_number(int(v)) 59 | } else { 60 | return nil, errors.New(`doubleToRawLongBits not implemented`) 61 | } 62 | } else if v, ok := rec.Value.(string); ok { 63 | len_ := len(v) 64 | if len_ > 0x400 { 65 | return nil, errors.New(`limited to 0x400 bytes`) 66 | } 67 | len_len := w.put_u32(uint32(len_)) 68 | w.put_bytes([]byte(v)) 69 | 70 | switch len_len { 71 | case 1: 72 | val_type = 8 73 | case 2: 74 | val_type = 9 75 | case 4: 76 | val_type = 10 77 | default: 78 | return nil, errors.New(`impossible`) 79 | } 80 | } else { 81 | return nil, errors.New(`wtf val_type: ` + fmt.Sprintf("%v", rec.Value)) 82 | } 83 | 84 | w.Data[first] = rec.ClassType | (val_type<<4 | (id_is_two_bytes)<<3) 85 | return w.Data, nil 86 | } 87 | 88 | func (rec *Record) Parse( 89 | data []byte, 90 | ) (int, error) { 91 | 92 | bf := ByteBuffer{Data: data} 93 | 94 | b1, e := bf.read_u8() 95 | if e != nil { 96 | return 0, e 97 | } 98 | class_type := b1 & 3 99 | if class_type > 2 { 100 | return 0, errors.New(`Invalid type`) 101 | } 102 | id_is_two_bytes := 1 103 | if (b1 & 8) == 0 { 104 | id_is_two_bytes = 0 105 | } 106 | var id int32 107 | if id_is_two_bytes == 0 { 108 | id, e = bf.read_n_bytes_as_int32(1) 109 | } else { 110 | id, e = bf.read_n_bytes_as_int32(2) 111 | } 112 | if e != nil { 113 | return 0, e 114 | } 115 | val_type := b1 >> 4 & 15 116 | if val_type > 10 { 117 | return 0, errors.New(`valt > 10`) 118 | } 119 | 120 | rec.ClassType = class_type 121 | rec.Id = id 122 | if b1&4 == 4 { 123 | rec.IsClassEnd = true 124 | } else { 125 | rec.IsClassEnd = false 126 | } 127 | 128 | switch val_type { 129 | case 0: 130 | rec.Value = nil 131 | return bf.offset, nil 132 | case 1: 133 | rec.Value = int32(0) 134 | return bf.offset, nil 135 | case 2: 136 | rec.Value = int32(1) 137 | return bf.offset, nil 138 | case 3: 139 | if b, e := bf.read_u8(); e == nil { 140 | rec.Value = int8(b) 141 | return bf.offset, nil 142 | } 143 | case 4: 144 | if b, e := bf.read_u16(); e == nil { 145 | rec.Value = int16(b) 146 | return bf.offset, nil 147 | } 148 | case 5: 149 | if b, e := bf.read_u32(); e == nil { 150 | rec.Value = int32(b) 151 | return bf.offset, nil 152 | } 153 | case 6: 154 | if b, e := bf.read_u64(); e == nil { 155 | rec.Value = int64(b) 156 | return bf.offset, nil 157 | } 158 | case 7: 159 | if b, e := bf.read_u64(); e == nil { 160 | rec.Value = float64(b) 161 | return bf.offset, nil 162 | } 163 | case 8: 164 | if len_, e := bf.read_n_bytes_as_int32(1); e == nil { 165 | if bs, e := bf.read_n(int(len_)); e == nil { 166 | rec.Value = string(bs) 167 | return bf.offset, nil 168 | } 169 | } 170 | case 9: 171 | if len_, e := bf.read_n_bytes_as_int32(2); e == nil { 172 | if bs, e := bf.read_n(int(len_)); e == nil { 173 | rec.Value = string(bs) 174 | return bf.offset, nil 175 | } 176 | } 177 | case 10: 178 | if len_, e := bf.read_n_bytes_as_int32(4); e == nil { 179 | if bs, e := bf.read_n(int(len_)); e == nil { 180 | rec.Value = string(bs) 181 | return bf.offset, nil 182 | } 183 | } 184 | } 185 | 186 | return 0, errors.New(`wtf err`) 187 | } 188 | -------------------------------------------------------------------------------- /crypto/crypto.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "ahex" 10 | "algo" 11 | "arand" 12 | "wa/def" 13 | "wa/signal/ecc" 14 | 15 | "golang.org/x/crypto/curve25519" 16 | ) 17 | 18 | func NewECKeyPair() ([]byte, []byte) { 19 | kp, _ := ecc.GenerateKeyPair() 20 | 21 | pub := kp.PublicKey().PublicKey() 22 | priv := kp.PrivateKey().Serialize() 23 | 24 | return priv[:], pub[:] 25 | } 26 | 27 | func Curve25519Agree(priv_me, pub_svr []byte) []byte { 28 | var priv [32]byte 29 | copy(priv[:], priv_me) 30 | var pub [32]byte 31 | copy(pub[:], pub_svr) 32 | 33 | var result [32]byte 34 | curve25519.ScalarMult(&result, &priv, &pub) 35 | return result[:] 36 | } 37 | 38 | func CalcToken(phone string, is_biz bool) []byte { 39 | password := []byte{} 40 | password = append(password, def.PKG_NAME(is_biz)...) 41 | password = append(password, def.ABOUT_LOGO(is_biz)...) 42 | derived := algo.PbkdfSha1(password, def.SALT, 0x40, 128) 43 | 44 | x := []byte{} 45 | x = append(x, def.SIGNATURE...) 46 | x = append(x, def.AppCodeHash(is_biz)...) 47 | x = append(x, []byte(phone)...) 48 | r := algo.HmacSha1(x, derived) 49 | 50 | return r 51 | } 52 | 53 | var Param_Order = []string{ 54 | "reason", 55 | "method", 56 | "read_phone_permission_granted", 57 | "lc", 58 | "offline_ab", 59 | "in", 60 | "backup_token", 61 | "lg", 62 | "e_regid", 63 | "mistyped", 64 | "id", 65 | "authkey", 66 | "e_skey_sig", 67 | "hasav", 68 | "action_taken", 69 | "token", 70 | "expid", 71 | "e_ident", 72 | "previous_screen", 73 | "rc", 74 | "sim_mcc", 75 | "simnum", 76 | "entered", 77 | "sim_state", 78 | "client_metrics", 79 | "cc", 80 | "e_skey_id", 81 | "mnc", 82 | "sim_mnc", 83 | "fdid", 84 | "funnel_id", 85 | "e_skey_val", 86 | "hasinrc", 87 | "network_radio_type", 88 | "mcc", 89 | "network_operator_name", 90 | "sim_operator_name", 91 | "e_keytype", 92 | "pid", 93 | "code", 94 | "current_screen", 95 | "vname", 96 | } 97 | 98 | func BuildUrl(param map[string]string) (string, error) { 99 | lst := []string{} 100 | for _, odr_key := range Param_Order { 101 | if v, ok := param[odr_key]; ok { 102 | lst = append(lst, odr_key+`=`+algo.UrlEnc(v)) 103 | } 104 | } 105 | if len(lst) != len(param) { 106 | return "", errors.New("some url param no defined in BuildUrl" + fmt.Sprintf("%v", lst) + ", " + fmt.Sprintf("%v", param)) 107 | } 108 | return strings.Join(lst, "&"), nil 109 | } 110 | 111 | func BE2U32(data []byte) uint32 { 112 | return binary.BigEndian.Uint32(data) 113 | } 114 | 115 | // U32 To BigEndian 116 | func U322BE(num uint32) []byte { 117 | buf := make([]byte, 4) 118 | binary.BigEndian.PutUint32(buf[0:], num) 119 | return buf 120 | } 121 | 122 | func LE2U8(data []byte) uint8 { 123 | return data[0] 124 | } 125 | func U82LE(num uint8) []byte { 126 | return []byte{num} 127 | } 128 | 129 | func LE2U16(data []byte) uint16 { 130 | return binary.LittleEndian.Uint16(data) 131 | } 132 | func U162LE(num uint16) []byte { 133 | buf := make([]byte, 2) 134 | binary.LittleEndian.PutUint16(buf[0:], num) 135 | return buf 136 | } 137 | 138 | func LE2U32(data []byte) uint32 { 139 | return binary.LittleEndian.Uint32(data) 140 | } 141 | func U322LE(num uint32) []byte { 142 | buf := make([]byte, 4) 143 | binary.LittleEndian.PutUint32(buf[0:], num) 144 | return buf 145 | } 146 | 147 | func LE2U64(data []byte) uint64 { 148 | return binary.LittleEndian.Uint64(data) 149 | } 150 | func U642LE(num uint64) []byte { 151 | buf := make([]byte, 8) 152 | binary.LittleEndian.PutUint64(buf[0:], num) 153 | return buf 154 | } 155 | 156 | // U32 To BigEndian (24 bit) 157 | func U322BE_24(num uint32) []byte { 158 | return U322BE(num)[1:] 159 | } 160 | 161 | // 3 bytes -> uint32 162 | func BE2U24(data []byte) uint32 { 163 | b := append([]byte{0}, data...) 164 | return binary.BigEndian.Uint32(b) 165 | } 166 | 167 | func RandomPadMsg(data []byte) []byte { 168 | { 169 | //color.HiMagenta(hex.Dump(data)) 170 | } 171 | 172 | padded := make([]byte, len(data)) 173 | copy(padded, data) 174 | 175 | pad_len := arand.Int(1, 0x10) // 1~15 176 | 177 | for i := 0; i < pad_len; i++ { 178 | padded = append(padded, byte(pad_len)) 179 | } 180 | return padded 181 | } 182 | func UnPadMsg(data []byte) ([]byte, error) { 183 | if len(data) < 1 { 184 | return nil, errors.New(`invalid data to unpad: ` + ahex.Enc(data)) 185 | } 186 | last := int(data[len(data)-1]) 187 | 188 | p := len(data) - 1 189 | for i := 0; i < last; i++ { 190 | if int(data[p]) != last { 191 | return nil, errors.New(`invalid data to unpad: ` + ahex.Enc(data)) 192 | } 193 | p-- 194 | } 195 | if len(data) < last { 196 | return nil, errors.New(`invalid data to unpad: ` + ahex.Enc(data)) 197 | } 198 | return data[0 : len(data)-last], nil 199 | } 200 | -------------------------------------------------------------------------------- /signal/protocol/sender_key_message.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | 7 | "wa/signal/ecc" 8 | "wa/signal/pb" 9 | "wa/signal/util/bytehelper" 10 | 11 | "github.com/golang/protobuf/proto" 12 | ) 13 | 14 | func NewSenderKeyMessageFromBytes(data []byte) (*SenderKeyMessage, error) { 15 | if len(data) < 65 { // 1byte ver, 64 byte signature 16 | return nil, errors.New(`skmsg too short`) 17 | } 18 | 19 | pb_body := data[1 : len(data)-64] 20 | p := &pb.SenderKeyMessage{} 21 | e := proto.Unmarshal(pb_body, p) 22 | if e != nil { 23 | return nil, e 24 | } 25 | 26 | ver, e := strconv.Atoi(string(data[0:1])) 27 | if e != nil { 28 | return nil, e 29 | } 30 | 31 | stru := &SenderKeyMessage{ 32 | P: p, 33 | version: uint32(ver), 34 | signature: data[len(data)-64:], 35 | } 36 | 37 | e = verify_sender_key_message(stru) 38 | if e != nil { 39 | return nil, e 40 | } 41 | 42 | return stru, nil 43 | } 44 | 45 | func verify_sender_key_message(msg *SenderKeyMessage) error { 46 | // Throw an error if the given message msg is an unsupported version. 47 | if msg.version <= UnsupportedVersion { 48 | err := "Legacy message: " + strconv.Itoa(int(msg.version)) 49 | return errors.New(err) 50 | } 51 | 52 | // Throw an error if the given message msg is a future version. 53 | if msg.version > CurrentVersion { 54 | err := "Unknown version: " + strconv.Itoa(int(msg.version)) 55 | return errors.New(err) 56 | } 57 | 58 | if len(msg.P.GetCiphertext()) == 0 { 59 | err := "Incomplete message." 60 | return errors.New(err) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // NewSenderKeyMessage returns a SenderKeyMessage. 67 | func NewSenderKeyMessage( 68 | keyID uint32, iteration uint32, ciphertext []byte, 69 | signatureKey ecc.ECPrivateKeyable, 70 | ) (*SenderKeyMessage, error) { 71 | 72 | // Ensure we have a valid signature key 73 | if signatureKey == nil { 74 | return nil, errors.New("Signature is nil") 75 | } 76 | 77 | // Build our SenderKeyMessage. 78 | msg := &SenderKeyMessage{ 79 | P: &pb.SenderKeyMessage{ 80 | Id: &keyID, 81 | Iteration: &iteration, 82 | Ciphertext: ciphertext, 83 | }, 84 | version: CurrentVersion, 85 | } 86 | 87 | // Sign the serialized message and include it in the message. This will be included 88 | // in the signed serialized version of the message. 89 | signature, e := ecc.CalculateSignature(signatureKey, msg.Serialize()) 90 | if e != nil { 91 | return nil, e 92 | } 93 | msg.signature = bytehelper.ArrayToSlice64(signature) 94 | 95 | return msg, nil 96 | } 97 | 98 | // SenderKeyMessage is a structure for messages using senderkey groups. 99 | type SenderKeyMessage struct { 100 | P *pb.SenderKeyMessage 101 | version uint32 102 | signature []byte 103 | } 104 | 105 | // KeyID returns the SenderKeyMessage key ID. 106 | func (p *SenderKeyMessage) KeyID() uint32 { 107 | return p.P.GetId() 108 | } 109 | 110 | // Iteration returns the SenderKeyMessage iteration. 111 | func (p *SenderKeyMessage) Iteration() uint32 { 112 | return p.P.GetIteration() 113 | } 114 | 115 | // Ciphertext returns the SenderKeyMessage encrypted ciphertext. 116 | func (p *SenderKeyMessage) Ciphertext() []byte { 117 | return p.P.GetCiphertext() 118 | } 119 | 120 | // Version returns the Signal message version of the message. 121 | func (p *SenderKeyMessage) Version() uint32 { 122 | return p.version 123 | } 124 | 125 | func (p *SenderKeyMessage) Serialize() []byte { 126 | bs, e := proto.Marshal(p.P) 127 | if e != nil { 128 | return nil 129 | } 130 | ver := []byte(strconv.Itoa(int(p.version))) 131 | ret := append(ver, bs...) 132 | return ret 133 | /* 134 | structure := &SenderKeyMessageStructure{ 135 | ID: p.keyID, 136 | Iteration: p.iteration, 137 | CipherText: p.ciphertext, 138 | 139 | Version: p.version, 140 | } 141 | return p.serializer.Serialize(structure) 142 | */ 143 | } 144 | 145 | func (p *SenderKeyMessage) SignedSerialize() []byte { 146 | bs, e := proto.Marshal(p.P) 147 | if e != nil { 148 | return nil 149 | } 150 | 151 | ver := []byte(strconv.Itoa(int(p.version))) 152 | ret := append(ver, bs...) 153 | ret = append(ret, p.signature...) 154 | return ret 155 | 156 | /* 157 | structure := &SenderKeyMessageStructure{ 158 | ID: p.keyID, 159 | Iteration: p.iteration, 160 | CipherText: p.ciphertext, 161 | Version: p.version, 162 | Signature: p.signature, 163 | } 164 | return p.serializer.Serialize(structure) 165 | */ 166 | } 167 | 168 | // Signature returns the SenderKeyMessage signature 169 | func (p *SenderKeyMessage) Signature() [64]byte { 170 | return bytehelper.SliceToArray64(p.signature) 171 | } 172 | 173 | // Type returns the sender key type. 174 | func (p *SenderKeyMessage) Type() uint32 { 175 | return SENDERKEY_TYPE 176 | } 177 | -------------------------------------------------------------------------------- /core/multi_device.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "wa/signal/protocol" 7 | "wa/xmpp" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | /* 13 | // remove 14 | { 15 | "Attrs": { 16 | "from": "2349159170075@s.whatsapp.net", 17 | "id": "2890645235", 18 | "t": "1640692855", 19 | "type": "devices" 20 | }, 21 | "Children": [ 22 | { 23 | "Attrs": { 24 | "device_hash": "2:0M1nDvxM" 25 | }, 26 | "Children": [ 27 | { 28 | "Attrs": { 29 | "jid": "2349159170075.0:3@s.whatsapp.net", 30 | "key-index": "1" 31 | }, 32 | "Tag": "device" 33 | }, 34 | { 35 | "Attrs": { 36 | "ts": "1640691730" 37 | }, 38 | "Tag": "key-index-list" 39 | } 40 | ], 41 | "Tag": "remove" 42 | } 43 | ], 44 | "Tag": "notification" 45 | } 46 | */ 47 | 48 | func New_Hook_MultiDeviceRemove(a *Acc) func(...any) error { 49 | return func(args ...any) error { 50 | n := args[0].(*xmpp.Node) 51 | 52 | attrs := n.MapAttrs() 53 | 54 | type_ := attrs[`type`] 55 | if type_ != `devices` { 56 | return nil 57 | } 58 | 59 | ch, ok := n.FindChildByTag(`remove`) 60 | if !ok { 61 | return nil 62 | } 63 | 64 | ch, ok = ch.FindChildByTag(`device`) 65 | if !ok { 66 | return nil 67 | } 68 | 69 | jid, ok := ch.GetAttr(`jid`) 70 | if !ok { 71 | return errors.New("no 'jid' attr") 72 | } 73 | 74 | recid, devid, e := split_jid(jid) 75 | if e != nil { 76 | return e 77 | } 78 | addr := protocol.NewSignalAddress( 79 | strconv.Itoa(int(recid)), devid) 80 | 81 | a.Store.DeleteIdentity(addr) 82 | a.Store.DeleteSession(addr) 83 | a.Store.DeleteSenderKey(addr) 84 | 85 | a.Store.DelMultiDevice(recid, devid) 86 | 87 | return nil 88 | } 89 | } 90 | 91 | /* 92 | // add 93 | 94 | { 95 | "Attrs": { 96 | "from": "2349159170075@s.whatsapp.net", 97 | "id": "1685660310", 98 | "t": "1640692882", 99 | "type": "devices" 100 | }, 101 | "Children": [ 102 | { 103 | "Attrs": { 104 | "device_hash": "2:LjbWaW6U" 105 | }, 106 | "Children": [ 107 | { 108 | "Attrs": { 109 | "jid": "2349159170075.0:4@s.whatsapp.net", 110 | "key-index": "1" 111 | }, 112 | "Tag": "device" 113 | }, 114 | { 115 | "Attrs": { 116 | "ts": "1640692878" 117 | }, 118 | "Data": "0a1208ddc1a4cc06108ef9ab8e0618012202000112407a28cfa688c13aeb3192063697797e275117d96a12f618e0ed8d196be09ceaabd649cf8892f1699ea53c1a53b2320edf81bbdc5b02857e211ea597a5e9e7be88", 119 | "Tag": "key-index-list" 120 | } 121 | ], 122 | "Tag": "add" 123 | } 124 | ], 125 | "Tag": "notification" 126 | } 127 | */ 128 | func New_Hook_MultiDeviceAdd(a *Acc) func(...any) error { 129 | return func(args ...any) error { 130 | n := args[0].(*xmpp.Node) 131 | 132 | attrs := n.MapAttrs() 133 | 134 | type_ := attrs[`type`] 135 | if type_ != `devices` { 136 | return nil 137 | } 138 | 139 | ch, ok := n.FindChildByTag(`add`) 140 | if !ok { 141 | return nil 142 | } 143 | 144 | ch, ok = ch.FindChildByTag(`device`) 145 | if !ok { 146 | return nil 147 | } 148 | 149 | jid, ok := ch.GetAttr(`jid`) 150 | if !ok { 151 | return errors.New("no 'jid' attr") 152 | } 153 | 154 | recid, devid, e := split_jid(jid) 155 | if e != nil { 156 | return e 157 | } 158 | 159 | return a.Store.AddMultiDevice(recid, devid) 160 | } 161 | } 162 | 163 | /* 164 | update: 165 | 166 | { 167 | "Attrs": { 168 | "from": "8613311112222@s.whatsapp.net", 169 | "id": "3012531595", 170 | "t": "1661436824", 171 | "type": "devices" 172 | }, 173 | "Children": [ 174 | { 175 | "Attrs": { 176 | "hash": "lWiW" 177 | }, 178 | "Tag": "update" 179 | } 180 | ], 181 | "Tag": "notification" 182 | } 183 | */ 184 | func New_Hook_MultiDeviceUpdate(a *Acc) func(...any) error { 185 | return func(args ...any) error { 186 | n := args[0].(*xmpp.Node) 187 | 188 | attrs := n.MapAttrs() 189 | 190 | type_ := attrs[`type`] 191 | if type_ != `devices` { 192 | return nil 193 | } 194 | 195 | _, ok := n.FindChildByTag(`update`) 196 | if !ok { 197 | return nil 198 | } 199 | 200 | from, ok := attrs[`from`] 201 | if !ok { 202 | return errors.New("no 'from' attr") 203 | } 204 | 205 | recid, _, e := split_jid(from) 206 | if e != nil { 207 | return e 208 | } 209 | 210 | // must use another goroutine, otherwise tcp would be blocking 211 | // usync() would freeze for 1 minute 212 | a.Wg.Add(1) 213 | go func() { 214 | defer a.Wg.Done() 215 | 216 | nr, e := a.usync_multi_device( 217 | fmt.Sprintf("%d@s.whatsapp.net", recid)) 218 | if e != nil { 219 | return 220 | } 221 | 222 | a.handle_usync_multi_device_result(nr, recid) 223 | }() 224 | 225 | return nil 226 | } 227 | } 228 | --------------------------------------------------------------------------------