mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
feat: cleaner auth state management
1. removes the "auth-state.update" event since it was bloated and fairly unnecessary 2. adds "creds.update" event that only sends the updated properties of the auth credentials 3. ensure the auth creds are not mutated anywhere, but only with the "creds.update" event 4. Separates the signal credentials from the auth credentials (kinda in progress) 5. in memory key store requires a save function to let one know when to save the keys !BREAKING_CHANGE
This commit is contained in:
133
src/Utils/auth-utils.ts
Normal file
133
src/Utils/auth-utils.ts
Normal file
@@ -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 }
|
||||
}
|
||||
@@ -8,4 +8,5 @@ export * from './signal'
|
||||
export * from './noise-handler'
|
||||
export * from './history'
|
||||
export * from './chat-utils'
|
||||
export * from './lt-hash'
|
||||
export * from './lt-hash'
|
||||
export * from './auth-utils'
|
||||
@@ -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),
|
||||
|
||||
@@ -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<SocketConfig, 'v
|
||||
}
|
||||
|
||||
export const generateRegistrationNode = (
|
||||
{ registrationId, signedPreKey, signedIdentityKey }: Pick<AuthenticationCreds, 'registrationId' | 'signedPreKey' | 'signedIdentityKey'>,
|
||||
{ registrationId, signedPreKey, signedIdentityKey }: SignalCreds,
|
||||
config: Pick<SocketConfig, 'version' | 'browser'>
|
||||
) => {
|
||||
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<AuthenticationCreds, 'advSecretKey' | 'signedIdentityKey' | 'signalIdentities'>
|
||||
|
||||
Reference in New Issue
Block a user