diff --git a/Example/example.ts b/Example/example.ts index 2f9832f..d3e48d8 100644 --- a/Example/example.ts +++ b/Example/example.ts @@ -1,41 +1,16 @@ -import { readFileSync, writeFileSync } from "fs" import P from "pino" import { Boom } from "@hapi/boom" -import makeWASocket, { AuthenticationState, DisconnectReason, AnyMessageContent, BufferJSON, initInMemoryKeyStore, delay } from '../src' +import makeWASocket, { DisconnectReason, AnyMessageContent, delay, useSingleFileAuthState } from '../src' -// load authentication state from a file -const loadState = () => { - let state: AuthenticationState | undefined = undefined - try { - const value = JSON.parse( - readFileSync('./auth_info_multi.json', { encoding: 'utf-8' }), - BufferJSON.reviver - ) - state = { - creds: value.creds, - // stores pre-keys, session & other keys in a JSON object - // we deserialize it here - keys: initInMemoryKeyStore(value.keys) - } - } catch{ } - return state -} -// save the authentication state to a file -const saveState = (state: any | undefined) => { - console.log('saving auth state') - writeFileSync( - './auth_info_multi.json', - // BufferJSON replacer utility saves buffers nicely - JSON.stringify(state, BufferJSON.replacer, 2) - ) -} +const { state, saveState } = useSingleFileAuthState('./auth_info_multi.json') // start a connection const startSock = () => { + const sock = makeWASocket({ logger: P({ level: 'trace' }), printQRInTerminal: true, - auth: loadState() + auth: state }) const sendMessageWTyping = async(msg: AnyMessageContent, jid: string) => { @@ -80,9 +55,8 @@ const startSock = () => { console.log('connection update', update) }) - // listen for when the auth state is updated - // it is imperative you save this data, it affects the signing keys you need to have conversations - sock.ev.on('auth-state.update', () => saveState(sock.authState)) + // listen for when the auth credentials is updated + sock.ev.on('creds.update', saveState) return sock } diff --git a/README.md b/README.md index 09ad0d2..b97fd3e 100644 --- a/README.md +++ b/README.md @@ -117,49 +117,22 @@ type SocketConfig = { You obviously don't want to keep scanning the QR code every time you want to connect. -So, you can save the credentials to log back in via: +So, you can load the credentials to log back in: ``` ts -import makeWASocket, { BufferJSON } from '@adiwajshing/baileys-md' +import makeWASocket, { BufferJSON, useSingleFileAuthState } from '@adiwajshing/baileys-md' import * as fs from 'fs' -// will initialize a default in-memory auth session -const conn = makeSocket() +// utility function to help save the auth state in a single file +// it's utility ends at demos -- as re-writing a large file over and over again is very inefficient +const { state, saveState } = useSingleFileAuthState('./auth_info_multi.json') +// will use the given state to connect +// so if valid credentials are available -- it'll connect without QR +const conn = makeSocket({ auth: state }) // this will be called as soon as the credentials are updated -conn.ev.on ('auth-state.update', () => { - // save credentials whenever updated - console.log (`credentials updated!`) - const authInfo = conn.authState // get all the auth info we need to restore this session - // save this info to a file - fs.writeFileSync( - './auth_info.json', - JSON.stringify(authInfo, BufferJSON.replacer, 2) - ) -}) +conn.ev.on ('creds.update', saveState) ``` -Then, to restore a session: -``` ts -import makeWASocket, { BufferJSON, initInMemoryKeyStore } from '@adiwajshing/baileys-md' -import * as fs from 'fs' - -const authJSON = JSON.parse( - fs.readFileSync( - './auth_info.json', - { encoding: 'utf-8' } - ), - BufferJSON.reviver -) -const auth = { - creds: authJSON.creds, - // stores pre-keys, session & other keys in a JSON object - // we deserialize it here - keys: initInMemoryKeyStore(authJSON.keys) -} -const conn = makeWASocket(auth) -// yay will connect without scanning QR -``` - -**Note**: Upon every successive connection, the auth state can update part of the stored credentials. It will also update when a message is received/sent due to signal sessions needing updating. Whenever that happens, the `auth-state.update` event is fired uploaded, and you must update your saved credentials upon receiving the event. Not doing so will prevent your messages from reaching the recipient & other unexpected consequences. +**Note**: When a message is received/sent, due to signal sessions needing updating, the auth keys (`authState.keys`) will update. Whenever that happens, you must save the updated keys. Not doing so will prevent your messages from reaching the recipient & other unexpected consequences. The `useSingleFileAuthState` function automatically takes care of that, but for any other serious implementation -- you will need to be very careful with the key state management. ## Listening to Connection Updates @@ -196,8 +169,8 @@ The events are typed up in a type map, as mentioned here: export type BaileysEventMap = { /** connection state has been updated -- WS closed, opened, connecting etc. */ 'connection.update': Partial - /** auth state updated -- some pre keys, or identity keys etc. */ - 'auth-state.update': AuthenticationState + /** auth credentials updated -- some pre key state, device ID etc. */ + 'creds.update': Partial /** set chats (history sync), messages are reverse chronologically sorted */ 'chats.set': { chats: Chat[], messages: WAMessage[] } /** upsert chats */ diff --git a/src/Defaults/index.ts b/src/Defaults/index.ts index 464369d..298d0aa 100644 --- a/src/Defaults/index.ts +++ b/src/Defaults/index.ts @@ -26,7 +26,7 @@ export const DEFAULT_CONNECTION_CONFIG: SocketConfig = { keepAliveIntervalMs: 25_000, logger: P().child({ class: 'baileys' }), printQRInTerminal: false, - emitOwnEvents: true + emitOwnEvents: true, } export const MEDIA_PATH_MAP: { [T in MediaType]: string } = { diff --git a/src/Socket/chats.ts b/src/Socket/chats.ts index 2329ad1..e4f63ff 100644 --- a/src/Socket/chats.ts +++ b/src/Socket/chats.ts @@ -219,7 +219,6 @@ export const makeChatsSocket = (config: SocketConfig) => { processSyncActions(newMutations) } } - ev.emit('auth-state.update', authState) } /** @@ -245,11 +244,12 @@ export const makeChatsSocket = (config: SocketConfig) => { } const sendPresenceUpdate = async(type: WAPresence, toJid?: string) => { + const me = authState.creds.me! if(type === 'available' || type === 'unavailable') { await sendNode({ tag: 'presence', attrs: { - name: authState.creds.me!.name, + name: me!.name, type } }) @@ -257,7 +257,7 @@ export const makeChatsSocket = (config: SocketConfig) => { await sendNode({ tag: 'chatstate', attrs: { - from: authState.creds.me!.id!, + from: me!.id!, to: toJid, }, content: [ @@ -344,8 +344,11 @@ export const makeChatsSocket = (config: SocketConfig) => { name: action.contactAction!.fullName } } else if(action?.pushNameSetting) { - authState.creds.me!.name = action?.pushNameSetting?.name! - ev.emit('auth-state.update', authState) + const me = { + ...authState.creds.me!, + name: action?.pushNameSetting?.name! + } + ev.emit('creds.update', { me }) } else { logger.warn({ action, id }, 'unprocessable update') } @@ -420,7 +423,6 @@ export const makeChatsSocket = (config: SocketConfig) => { await query(node) await authState.keys.setAppStateSyncVersion(name, state) - ev.emit('auth-state.update', authState) if(config.emitOwnEvents) { processSyncActions(result.newMutations) diff --git a/src/Socket/messages-recv.ts b/src/Socket/messages-recv.ts index 6db257d..9ad9aee 100644 --- a/src/Socket/messages-recv.ts +++ b/src/Socket/messages-recv.ts @@ -158,15 +158,16 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { logger.info({ with: message.key.remoteJid! }, 'shared key') break case proto.ProtocolMessage.ProtocolMessageType.APP_STATE_SYNC_KEY_SHARE: + let newAppStateSyncKeyId = '' for(const { keyData, keyId } of protocolMsg.appStateSyncKeyShare!.keys || []) { const str = Buffer.from(keyId.keyId!).toString('base64') logger.info({ str }, 'injecting new app state sync key') await authState.keys.setAppStateSyncKey(str, keyData) - authState.creds.myAppStateKeyId = str + newAppStateSyncKeyId = str } - ev.emit('auth-state.update', authState) + ev.emit('creds.update', { myAppStateKeyId: newAppStateSyncKeyId }) resyncMainAppState() break @@ -359,7 +360,6 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { } // if there were some successful decryptions if(dec.successes.length) { - ev.emit('auth-state.update', authState) // send message receipt let recpAttrs: { [_: string]: any } if(isMe) { @@ -506,8 +506,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => { contactNameUpdates[jid] = msg.pushName // update our pushname too if(msg.key.fromMe && authState.creds.me?.name !== msg.pushName) { - authState.creds.me!.name = msg.pushName - ev.emit('auth-state.update', authState) + ev.emit('creds.update', { me: { ...authState.creds.me!, name: msg.pushName! } }) } } diff --git a/src/Socket/messages-send.ts b/src/Socket/messages-send.ts index 7046e0f..56eb181 100644 --- a/src/Socket/messages-send.ts +++ b/src/Socket/messages-send.ts @@ -259,7 +259,7 @@ export const makeMessagesSocket = (config: SocketConfig) => { const destinationJid = jidEncode(user, isGroup ? 'g.us' : 's.whatsapp.net') if(isGroup) { - const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState) + const { ciphertext, senderKeyDistributionMessageKey } = await encryptSenderKeyMsgSignalProto(destinationJid, encodedMsg, authState.creds.me!.id, authState) let groupData = cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined if(!groupData) groupData = await groupMetadata(jid) @@ -375,7 +375,6 @@ export const makeMessagesSocket = (config: SocketConfig) => { await sendNode(stanza) - ev.emit('auth-state.update', authState) return msgId } diff --git a/src/Socket/socket.ts b/src/Socket/socket.ts index 06dd674..a3b4f1d 100644 --- a/src/Socket/socket.ts +++ b/src/Socket/socket.ts @@ -4,10 +4,11 @@ import { promisify } from "util" import WebSocket from "ws" import { randomBytes } from 'crypto' import { proto } from '../../WAProto' -import { DisconnectReason, SocketConfig, BaileysEventEmitter, ConnectionState } from "../Types" -import { Curve, initAuthState, generateRegistrationNode, configureSuccessfulPairing, generateLoginNode, encodeBigEndian, promiseTimeout, generateOrGetPreKeys, xmppSignedPreKey, xmppPreKey, getPreKeys, makeNoiseHandler } from "../Utils" +import { DisconnectReason, SocketConfig, BaileysEventEmitter, ConnectionState, AuthenticationCreds } from "../Types" +import { Curve, generateRegistrationNode, configureSuccessfulPairing, generateLoginNode, encodeBigEndian, promiseTimeout, generateOrGetPreKeys, xmppSignedPreKey, xmppPreKey, getPreKeys, makeNoiseHandler, useSingleFileAuthState } from "../Utils" import { DEFAULT_ORIGIN, DEF_TAG_PREFIX, DEF_CALLBACK_PREFIX, KEY_BUNDLE_TYPE } from "../Defaults" import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, S_WHATSAPP_NET, getBinaryNodeChild } from '../WABinary' + /** * Connects to WA servers and performs: * - simple queries (no retry mechanism, wait for connection establishment) @@ -39,13 +40,22 @@ export const makeSocket = ({ } }) ws.setMaxListeners(0) + const ev = new EventEmitter() as BaileysEventEmitter /** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */ const ephemeralKeyPair = Curve.generateKeyPair() /** WA noise protocol wrapper */ const noise = makeNoiseHandler(ephemeralKeyPair) - const authState = initialAuthState || initAuthState() + let authState = initialAuthState + if(!authState) { + authState = useSingleFileAuthState('./auth-info-multi.json').state + + logger.warn(` + Baileys just created a single file state for your credentials. + This will not be supported soon. + Please pass the credentials in the config itself + `) + } const { creds } = authState - const ev = new EventEmitter() as BaileysEventEmitter let lastDateRecv: Date let epoch = 0 @@ -174,11 +184,16 @@ export const makeSocket = ({ } /** get some pre-keys and do something with them */ const assertingPreKeys = async(range: number, execute: (keys: { [_: number]: any }) => Promise) => { - const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState, range) - - creds.serverHasPreKeys = true - creds.nextPreKeyId = Math.max(lastPreKeyId+1, creds.nextPreKeyId) - creds.firstUnuploadedPreKeyId = Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId+1) + const { newPreKeys, lastPreKeyId, preKeysRange } = generateOrGetPreKeys(authState.creds, range) + + const update: Partial = { + nextPreKeyId: Math.max(lastPreKeyId+1, creds.nextPreKeyId), + firstUnuploadedPreKeyId: Math.max(creds.firstUnuploadedPreKeyId, lastPreKeyId+1) + } + if(!creds.serverHasPreKeys) { + update.serverHasPreKeys = true + } + await Promise.all( Object.keys(newPreKeys).map(k => authState.keys.setPreKey(+k, newPreKeys[+k])) ) @@ -186,7 +201,7 @@ export const makeSocket = ({ const preKeys = await getPreKeys(authState.keys, preKeysRange[0], preKeysRange[0] + preKeysRange[1]) await execute(preKeys) - ev.emit('auth-state.update', authState) + ev.emit('creds.update', update) } /** generates and uploads a set of pre-keys */ const uploadPreKeys = async() => { @@ -464,10 +479,10 @@ export const makeSocket = ({ throw new Boom('Authentication failed', { statusCode: +(value.attrs.code || 500) }) } } - Object.assign(creds, updatedCreds) - logger.info({ jid: creds.me!.id }, 'registered connection, restart server') - ev.emit('auth-state.update', authState) + logger.info({ jid: updatedCreds.me!.id }, 'registered connection, restart server') + + ev.emit('creds.update', updatedCreds) ev.emit('connection.update', { isNewLogin: true, qr: undefined }) end(new Boom('Restart Required', { statusCode: DisconnectReason.restartRequired })) @@ -512,6 +527,8 @@ export const makeSocket = ({ process.nextTick(() => { ev.emit('connection.update', { connection: 'connecting', receivedPendingNotifications: false, qr: undefined }) }) + // update credentials when required + ev.on('creds.update', update => Object.assign(creds, update)) return { ws, diff --git a/src/Types/Auth.ts b/src/Types/Auth.ts index 0e47fd8..5ff95fe 100644 --- a/src/Types/Auth.ts +++ b/src/Types/Auth.ts @@ -16,12 +16,16 @@ export type SignalIdentity = { export type LTHashState = { version: number, hash: Buffer, mutations: ChatMutation[] } -export type AuthenticationCreds = { - noiseKey: KeyPair - signedIdentityKey: KeyPair - signedPreKey: SignedKeyPair - registrationId: number - advSecretKey: string +export type SignalCreds = { + readonly signedIdentityKey: KeyPair + readonly signedPreKey: SignedKeyPair + readonly registrationId: number +} + +export type AuthenticationCreds = SignalCreds & { + readonly noiseKey: KeyPair + readonly advSecretKey: string + me?: Contact account?: proto.IADVSignedDeviceIdentity signalIdentities?: SignalIdentity[] @@ -49,6 +53,11 @@ export type SignalKeyStore = { setAppStateSyncVersion: (id: WAPatchName, item: LTHashState) => Awaitable } +export type SignalAuthState = { + creds: SignalCreds + keys: SignalKeyStore +} + export type AuthenticationState = { creds: AuthenticationCreds keys: SignalKeyStore diff --git a/src/Types/index.ts b/src/Types/index.ts index a8e3f77..1c69c2d 100644 --- a/src/Types/index.ts +++ b/src/Types/index.ts @@ -11,7 +11,7 @@ import type { Logger } from "pino" import type { URL } from "url" import type NodeCache from 'node-cache' -import { AuthenticationState } from './Auth' +import { AuthenticationState, AuthenticationCreds } from './Auth' import { Chat, PresenceData } from './Chat' import { Contact } from './Contact' import { ConnectionState } from './State' @@ -96,8 +96,8 @@ export type CurveKeyPair = { private: Uint8Array; public: Uint8Array } export type BaileysEventMap = { /** connection state has been updated -- WS closed, opened, connecting etc. */ 'connection.update': Partial - /** auth state updated -- some pre keys, or identity keys etc. */ - 'auth-state.update': AuthenticationState + /** credentials updated -- some metadata, keys or something */ + 'creds.update': Partial /** set chats (history sync), messages are reverse chronologically sorted */ 'chats.set': { chats: Chat[], messages: WAMessage[] } /** upsert chats */ diff --git a/src/Utils/auth-utils.ts b/src/Utils/auth-utils.ts new file mode 100644 index 0000000..16e6b4a --- /dev/null +++ b/src/Utils/auth-utils.ts @@ -0,0 +1,133 @@ +import { randomBytes } from 'crypto' +import { proto } from '../../WAProto' +import type { SignalKeyStore, AuthenticationCreds, KeyPair, LTHashState, AuthenticationState } from "../Types" +import { Curve, signedKeyPair } from './crypto' +import { generateRegistrationId, BufferJSON } from './generics' + +export const initInMemoryKeyStore = ( + { preKeys, sessions, senderKeys, appStateSyncKeys, appStateVersions }: { + preKeys?: { [k: number]: KeyPair }, + sessions?: { [k: string]: any }, + senderKeys?: { [k: string]: any } + appStateSyncKeys?: { [k: string]: proto.IAppStateSyncKeyData }, + appStateVersions?: { [k: string]: LTHashState }, + } = { }, + save: (data: any) => void +) => { + + preKeys = preKeys || { } + sessions = sessions || { } + senderKeys = senderKeys || { } + appStateSyncKeys = appStateSyncKeys || { } + appStateVersions = appStateVersions || { } + + const keyData = { + preKeys, + sessions, + senderKeys, + appStateSyncKeys, + appStateVersions, + } + + return { + ...keyData, + getPreKey: keyId => preKeys[keyId], + setPreKey: (keyId, pair) => { + if(pair) preKeys[keyId] = pair + else delete preKeys[keyId] + + save(keyData) + }, + getSession: id => sessions[id], + setSession: (id, item) => { + if(item) sessions[id] = item + else delete sessions[id] + + save(keyData) + }, + getSenderKey: id => { + return senderKeys[id] + }, + setSenderKey: (id, item) => { + if(item) senderKeys[id] = item + else delete senderKeys[id] + + save(keyData) + }, + getAppStateSyncKey: id => { + const obj = appStateSyncKeys[id] + if(obj) { + return proto.AppStateSyncKeyData.fromObject(obj) + } + }, + setAppStateSyncKey: (id, item) => { + if(item) appStateSyncKeys[id] = item + else delete appStateSyncKeys[id] + + save(keyData) + }, + getAppStateSyncVersion: id => { + const obj = appStateVersions[id] + if(obj) { + return obj + } + }, + setAppStateSyncVersion: (id, item) => { + if(item) appStateVersions[id] = item + else delete appStateVersions[id] + + save(keyData) + } + } as SignalKeyStore +} + +export const initAuthCreds = (): AuthenticationCreds => { + const identityKey = Curve.generateKeyPair() + return { + noiseKey: Curve.generateKeyPair(), + signedIdentityKey: identityKey, + signedPreKey: signedKeyPair(identityKey, 1), + registrationId: generateRegistrationId(), + advSecretKey: randomBytes(32).toString('base64'), + + nextPreKeyId: 1, + firstUnuploadedPreKeyId: 1, + serverHasPreKeys: false + } +} +/** stores the full authentication state in a single JSON file */ +export const useSingleFileAuthState = (filename: string) => { + // require fs here so that in case "fs" is not available -- the app does not crash + const { readFileSync, writeFileSync, existsSync } = require('fs') + + let state: AuthenticationState = undefined + + // save the authentication state to a file + const saveState = () => { + console.log('saving auth state') + writeFileSync( + filename, + // BufferJSON replacer utility saves buffers nicely + JSON.stringify(state, BufferJSON.replacer, 2) + ) + } + + if(existsSync(filename)) { + const { creds, keys } = JSON.parse( + readFileSync(filename, { encoding: 'utf-8' }), + BufferJSON.reviver + ) + state = { + creds: creds, + // stores pre-keys, session & other keys in a JSON object + // we deserialize it here + keys: initInMemoryKeyStore(keys, saveState) + } + } else { + const creds = initAuthCreds() + const keys = initInMemoryKeyStore({ }, saveState) + state = { creds: creds, keys: keys } + } + + return { state, saveState } +} \ No newline at end of file diff --git a/src/Utils/index.ts b/src/Utils/index.ts index 640c9f2..a4c41fe 100644 --- a/src/Utils/index.ts +++ b/src/Utils/index.ts @@ -8,4 +8,5 @@ export * from './signal' export * from './noise-handler' export * from './history' export * from './chat-utils' -export * from './lt-hash' \ No newline at end of file +export * from './lt-hash' +export * from './auth-utils' \ No newline at end of file diff --git a/src/Utils/signal.ts b/src/Utils/signal.ts index c7c90c8..7caa4d7 100644 --- a/src/Utils/signal.ts +++ b/src/Utils/signal.ts @@ -2,7 +2,7 @@ import * as libsignal from 'libsignal' import { encodeBigEndian } from "./generics" import { Curve } from "./crypto" import { SenderKeyDistributionMessage, GroupSessionBuilder, SenderKeyRecord, SenderKeyName, GroupCipher } from '../../WASignalGroup' -import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, AuthenticationState } from "../Types/Auth" +import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, SignalAuthState, AuthenticationCreds } from "../Types/Auth" import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildUInt, jidDecode, JidWithDevice } from "../WABinary" import { proto } from "../../WAProto" @@ -42,7 +42,7 @@ export const getPreKeys = async({ getPreKey }: SignalKeyStore, min: number, limi return dict } -export const generateOrGetPreKeys = ({ creds }: AuthenticationState, range: number) => { +export const generateOrGetPreKeys = (creds: AuthenticationCreds, range: number) => { const avaliable = creds.nextPreKeyId - creds.firstUnuploadedPreKeyId const remaining = range - avaliable const lastPreKeyId = creds.nextPreKeyId + remaining - 1 @@ -83,7 +83,7 @@ export const xmppPreKey = (pair: KeyPair, id: number): BinaryNode => ( } ) -export const signalStorage = ({ creds, keys }: AuthenticationState) => ({ +export const signalStorage = ({ creds, keys }: SignalAuthState) => ({ loadSession: async id => { const sess = await keys.getSession(id) if(sess) { @@ -132,7 +132,7 @@ export const signalStorage = ({ creds, keys }: AuthenticationState) => ({ } }) -export const decryptGroupSignalProto = (group: string, user: string, msg: Buffer | Uint8Array, auth: AuthenticationState) => { +export const decryptGroupSignalProto = (group: string, user: string, msg: Buffer | Uint8Array, auth: SignalAuthState) => { const senderName = jidToSignalSenderKeyName(group, user) const cipher = new GroupCipher(signalStorage(auth), senderName) @@ -142,7 +142,7 @@ export const decryptGroupSignalProto = (group: string, user: string, msg: Buffer export const processSenderKeyMessage = async( authorJid: string, item: proto.ISenderKeyDistributionMessage, - auth: AuthenticationState + auth: SignalAuthState ) => { const builder = new GroupSessionBuilder(signalStorage(auth)) const senderName = jidToSignalSenderKeyName(item.groupId, authorJid) @@ -156,7 +156,7 @@ export const processSenderKeyMessage = async( await builder.process(senderName, senderMsg) } -export const decryptSignalProto = async(user: string, type: 'pkmsg' | 'msg', msg: Buffer | Uint8Array, auth: AuthenticationState) => { +export const decryptSignalProto = async(user: string, type: 'pkmsg' | 'msg', msg: Buffer | Uint8Array, auth: SignalAuthState) => { const addr = jidToSignalProtocolAddress(user) const session = new libsignal.SessionCipher(signalStorage(auth), addr) let result: Buffer @@ -172,7 +172,7 @@ export const decryptSignalProto = async(user: string, type: 'pkmsg' | 'msg', msg } -export const encryptSignalProto = async(user: string, buffer: Buffer, auth: AuthenticationState) => { +export const encryptSignalProto = async(user: string, buffer: Buffer, auth: SignalAuthState) => { const addr = jidToSignalProtocolAddress(user) const cipher = new libsignal.SessionCipher(signalStorage(auth), addr) @@ -183,9 +183,9 @@ export const encryptSignalProto = async(user: string, buffer: Buffer, auth: Auth } } -export const encryptSenderKeyMsgSignalProto = async(group: string, data: Uint8Array | Buffer, auth: AuthenticationState) => { +export const encryptSenderKeyMsgSignalProto = async(group: string, data: Uint8Array | Buffer, meId: string, auth: SignalAuthState) => { const storage = signalStorage(auth) - const senderName = jidToSignalSenderKeyName(group, auth.creds.me!.id) + const senderName = jidToSignalSenderKeyName(group, meId) const builder = new GroupSessionBuilder(storage) const senderKey = await auth.keys.getSenderKey(senderName) @@ -202,7 +202,7 @@ export const encryptSenderKeyMsgSignalProto = async(group: string, data: Uint8Ar } } -export const parseAndInjectE2ESession = async(node: BinaryNode, auth: AuthenticationState) => { +export const parseAndInjectE2ESession = async(node: BinaryNode, auth: SignalAuthState) => { const extractKey = (key: BinaryNode) => ( key ? ({ keyId: getBinaryNodeChildUInt(key, 'id', 3), diff --git a/src/Utils/validate-connection.ts b/src/Utils/validate-connection.ts index f09c304..88468b8 100644 --- a/src/Utils/validate-connection.ts +++ b/src/Utils/validate-connection.ts @@ -1,9 +1,8 @@ import { Boom } from '@hapi/boom' -import { randomBytes } from 'crypto' import { proto } from '../../WAProto' -import type { AuthenticationState, SocketConfig, SignalKeyStore, AuthenticationCreds, KeyPair, LTHashState } from "../Types" -import { Curve, hmacSign, signedKeyPair } from './crypto' -import { encodeInt, generateRegistrationId } from './generics' +import type { SocketConfig, AuthenticationCreds, SignalCreds } from "../Types" +import { Curve, hmacSign } from './crypto' +import { encodeInt } from './generics' import { BinaryNode, S_WHATSAPP_NET, jidDecode, Binary, getAllBinaryNodeChildren } from '../WABinary' import { createSignalIdentity } from './signal' @@ -41,7 +40,7 @@ export const generateLoginNode = (userJid: string, config: Pick, + { registrationId, signedPreKey, signedIdentityKey }: SignalCreds, config: Pick ) => { const appVersionBuf = new Uint8Array(Buffer.from(ENCODED_VERSION, "base64")); @@ -82,84 +81,6 @@ export const generateRegistrationNode = ( return proto.ClientPayload.encode(registerPayload).finish() } -export const initInMemoryKeyStore = ( - { preKeys, sessions, senderKeys, appStateSyncKeys, appStateVersions }: { - preKeys?: { [k: number]: KeyPair }, - sessions?: { [k: string]: any }, - senderKeys?: { [k: string]: any } - appStateSyncKeys?: { [k: string]: proto.IAppStateSyncKeyData }, - appStateVersions?: { [k: string]: LTHashState }, - } = { }, -) => { - preKeys = preKeys || { } - sessions = sessions || { } - senderKeys = senderKeys || { } - appStateSyncKeys = appStateSyncKeys || { } - appStateVersions = appStateVersions || { } - return { - preKeys, - sessions, - senderKeys, - appStateSyncKeys, - appStateVersions, - getPreKey: keyId => preKeys[keyId], - setPreKey: (keyId, pair) => { - if(pair) preKeys[keyId] = pair - else delete preKeys[keyId] - }, - getSession: id => sessions[id], - setSession: (id, item) => { - if(item) sessions[id] = item - else delete sessions[id] - }, - getSenderKey: id => { - return senderKeys[id] - }, - setSenderKey: (id, item) => { - if(item) senderKeys[id] = item - else delete senderKeys[id] - }, - getAppStateSyncKey: id => { - const obj = appStateSyncKeys[id] - if(obj) { - return proto.AppStateSyncKeyData.fromObject(obj) - } - }, - setAppStateSyncKey: (id, item) => { - if(item) appStateSyncKeys[id] = item - else delete appStateSyncKeys[id] - }, - getAppStateSyncVersion: id => { - const obj = appStateVersions[id] - if(obj) { - return obj - } - }, - setAppStateSyncVersion: (id, item) => { - if(item) appStateVersions[id] = item - else delete appStateVersions[id] - } - } as SignalKeyStore -} - -export const initAuthState = (): AuthenticationState => { - const identityKey = Curve.generateKeyPair() - return { - creds: { - noiseKey: Curve.generateKeyPair(), - signedIdentityKey: identityKey, - signedPreKey: signedKeyPair(identityKey, 1), - registrationId: generateRegistrationId(), - advSecretKey: randomBytes(32).toString('base64'), - - nextPreKeyId: 1, - firstUnuploadedPreKeyId: 1, - serverHasPreKeys: false - }, - keys: initInMemoryKeyStore() - } -} - export const configureSuccessfulPairing = ( stanza: BinaryNode, { advSecretKey, signedIdentityKey, signalIdentities }: Pick