mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
separate out crypto utils
This commit is contained in:
@@ -78,9 +78,10 @@ If the connection is successful, you will see a QR code printed on your terminal
|
|||||||
|
|
||||||
## Notable Differences Between Baileys Web & MD
|
## Notable Differences Between Baileys Web & MD
|
||||||
|
|
||||||
1. Baileys no longer maintains an internal state of chats/contacts/messages. You must take this on your own, simply because your state in MD is its own source of truth & there is no one-size-fits-all way to handle the storage for this.
|
1. Baileys has been written from the ground up to have a more "functional" structure. This is done primarily for simplicity & more testability
|
||||||
2. A baileys "socket" is meant to be a temporary & disposable object -- this is done to maintain simplicity & prevent bugs. I felt the entire Baileys object became too bloated as it supported too many configurations.
|
2. Baileys no longer maintains an internal state of chats/contacts/messages. You must take this on your own, simply because your state in MD is its own source of truth & there is no one-size-fits-all way to handle the storage for this.
|
||||||
3. Moreover, Baileys does not offer an inbuilt reconnect mechanism anymore (though it's super easy to set one up on your own with your own rules, check the example script)
|
3. A baileys "socket" is meant to be a temporary & disposable object -- this is done to maintain simplicity & prevent bugs. I felt the entire Baileys object became too bloated as it supported too many configurations. You're encouraged to write your own implementation to handle missing functionality.
|
||||||
|
4. Moreover, Baileys does not offer an inbuilt reconnect mechanism anymore (though it's super easy to set one up on your own with your own rules, check the example script)
|
||||||
|
|
||||||
## Configuring the Connection
|
## Configuring the Connection
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidNormalizedUse
|
|||||||
import { makeSocket } from "./socket";
|
import { makeSocket } from "./socket";
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { toNumber } from "../Utils/generics";
|
import { toNumber } from "../Utils/generics";
|
||||||
import { compressImage, generateProfilePicture } from "..";
|
import { generateProfilePicture } from "../Utils";
|
||||||
|
|
||||||
export const makeChatsSocket = (config: SocketConfig) => {
|
export const makeChatsSocket = (config: SocketConfig) => {
|
||||||
const { logger } = config
|
const { logger } = config
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
import { makeGroupsSocket } from "./groups"
|
import { makeGroupsSocket } from "./groups"
|
||||||
import { SocketConfig, WAMessageStubType, ParticipantAction, Chat, GroupMetadata } from "../Types"
|
import { SocketConfig, WAMessageStubType, ParticipantAction, Chat, GroupMetadata } from "../Types"
|
||||||
import { decodeMessageStanza, encodeBigEndian, toNumber, whatsappID } from "../Utils"
|
import { decodeMessageStanza, encodeBigEndian, toNumber } from "../Utils"
|
||||||
import { BinaryNode, jidDecode, jidEncode, isJidStatusBroadcast, S_WHATSAPP_NET, areJidsSameUser, getBinaryNodeChildren, getBinaryNodeChild } from '../WABinary'
|
import { BinaryNode, jidDecode, jidEncode, isJidStatusBroadcast, areJidsSameUser, getBinaryNodeChildren } from '../WABinary'
|
||||||
import { downloadIfHistory } from '../Utils/history'
|
import { downloadIfHistory } from '../Utils/history'
|
||||||
import { proto } from "../../WAProto"
|
import { proto } from "../../WAProto"
|
||||||
import { generateSignalPubKey, xmppPreKey, xmppSignedPreKey } from "../Utils/signal"
|
import { generateSignalPubKey, xmppPreKey, xmppSignedPreKey } from "../Utils/signal"
|
||||||
@@ -139,7 +139,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
switch (message.messageStubType) {
|
switch (message.messageStubType) {
|
||||||
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
case WAMessageStubType.GROUP_PARTICIPANT_LEAVE:
|
||||||
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
case WAMessageStubType.GROUP_PARTICIPANT_REMOVE:
|
||||||
participants = message.messageStubParameters.map(whatsappID)
|
participants = message.messageStubParameters
|
||||||
emitParticipantsUpdate('remove')
|
emitParticipantsUpdate('remove')
|
||||||
// mark the chat read only if you left the group
|
// mark the chat read only if you left the group
|
||||||
if (participants.includes(meJid)) {
|
if (participants.includes(meJid)) {
|
||||||
@@ -149,7 +149,7 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
case WAMessageStubType.GROUP_PARTICIPANT_ADD:
|
||||||
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
case WAMessageStubType.GROUP_PARTICIPANT_INVITE:
|
||||||
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
case WAMessageStubType.GROUP_PARTICIPANT_ADD_REQUEST_JOIN:
|
||||||
participants = message.messageStubParameters.map(whatsappID)
|
participants = message.messageStubParameters
|
||||||
if (participants.includes(meJid)) {
|
if (participants.includes(meJid)) {
|
||||||
chatUpdate.readOnly = false
|
chatUpdate.readOnly = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import WebSocket from "ws"
|
|||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { DisconnectReason, SocketConfig, BaileysEventEmitter } from "../Types"
|
import { DisconnectReason, SocketConfig, BaileysEventEmitter } from "../Types"
|
||||||
import { generateCurveKeyPair, initAuthState, generateRegistrationNode, configureSuccessfulPairing, generateLoginNode, encodeBigEndian, promiseTimeout } from "../Utils"
|
import { Curve, initAuthState, generateRegistrationNode, configureSuccessfulPairing, generateLoginNode, encodeBigEndian, promiseTimeout } from "../Utils"
|
||||||
import { DEFAULT_ORIGIN, DEF_TAG_PREFIX, DEF_CALLBACK_PREFIX, KEY_BUNDLE_TYPE } from "../Defaults"
|
import { DEFAULT_ORIGIN, DEF_TAG_PREFIX, DEF_CALLBACK_PREFIX, KEY_BUNDLE_TYPE } from "../Defaults"
|
||||||
import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, S_WHATSAPP_NET } from '../WABinary'
|
import { assertNodeErrorFree, BinaryNode, encodeBinaryNode, S_WHATSAPP_NET } from '../WABinary'
|
||||||
import noiseHandler from '../Utils/noise-handler'
|
import noiseHandler from '../Utils/noise-handler'
|
||||||
@@ -42,7 +42,7 @@ export const makeSocket = ({
|
|||||||
})
|
})
|
||||||
ws.setMaxListeners(0)
|
ws.setMaxListeners(0)
|
||||||
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
/** ephemeral key pair used to encrypt/decrypt communication. Unique for each connection */
|
||||||
const ephemeralKeyPair = generateCurveKeyPair()
|
const ephemeralKeyPair = Curve.generateKeyPair()
|
||||||
/** WA noise protocol wrapper */
|
/** WA noise protocol wrapper */
|
||||||
const noise = noiseHandler(ephemeralKeyPair)
|
const noise = noiseHandler(ephemeralKeyPair)
|
||||||
const authState = initialAuthState || initAuthState()
|
const authState = initialAuthState || initAuthState()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import { aesDecrypt, hmacSign, aesEncrypt, hkdf } from "./generics"
|
import { aesDecrypt, hmacSign, aesEncrypt, hkdf } from "./crypto"
|
||||||
import { AuthenticationState, ChatModification } from "../Types"
|
import { AuthenticationState, ChatModification } from "../Types"
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { LT_HASH_ANTI_TAMPERING } from '../WABinary/LTHash'
|
import { LT_HASH_ANTI_TAMPERING } from '../WABinary/LTHash'
|
||||||
|
|||||||
100
src/Utils/crypto.ts
Normal file
100
src/Utils/crypto.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import CurveCrypto from 'libsignal/src/curve25519_wrapper'
|
||||||
|
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto'
|
||||||
|
import { KeyPair } from '../Types'
|
||||||
|
|
||||||
|
export const Curve = {
|
||||||
|
generateKeyPair: (): KeyPair => {
|
||||||
|
const { pubKey, privKey } = CurveCrypto.keyPair(randomBytes(32))
|
||||||
|
return {
|
||||||
|
private: Buffer.from(privKey),
|
||||||
|
public: Buffer.from(pubKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => {
|
||||||
|
const shared = CurveCrypto.sharedSecret(publicKey, privateKey)
|
||||||
|
return Buffer.from(shared)
|
||||||
|
},
|
||||||
|
sign: (privateKey: Uint8Array, buf: Uint8Array) => (
|
||||||
|
Buffer.from(CurveCrypto.sign(privateKey, buf))
|
||||||
|
),
|
||||||
|
verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
|
||||||
|
try {
|
||||||
|
CurveCrypto.verify(pubKey, message, signature)
|
||||||
|
return true
|
||||||
|
} catch(error) {
|
||||||
|
if(error.message.includes('Invalid')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const signedKeyPair = (keyPair: KeyPair, keyId: number) => {
|
||||||
|
const signKeys = Curve.generateKeyPair()
|
||||||
|
const pubKey = new Uint8Array(33)
|
||||||
|
pubKey.set([5], 0)
|
||||||
|
pubKey.set(signKeys.public, 1)
|
||||||
|
|
||||||
|
const signature = Curve.sign(keyPair.private, pubKey)
|
||||||
|
|
||||||
|
return { keyPair: signKeys, signature, keyId }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
||||||
|
export function aesDecrypt(buffer: Buffer, key: Buffer) {
|
||||||
|
return aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0, 16))
|
||||||
|
}
|
||||||
|
/** decrypt AES 256 CBC */
|
||||||
|
export function aesDecryptWithIV(buffer: Buffer, key: Buffer, IV: Buffer) {
|
||||||
|
const aes = createDecipheriv('aes-256-cbc', key, IV)
|
||||||
|
return Buffer.concat([aes.update(buffer), aes.final()])
|
||||||
|
}
|
||||||
|
// encrypt AES 256 CBC; where a random IV is prefixed to the buffer
|
||||||
|
export function aesEncrypt(buffer: Buffer | Uint8Array, key: Buffer) {
|
||||||
|
const IV = randomBytes(16)
|
||||||
|
const aes = createCipheriv('aes-256-cbc', key, IV)
|
||||||
|
return Buffer.concat([IV, aes.update(buffer), aes.final()]) // prefix IV to the buffer
|
||||||
|
}
|
||||||
|
// encrypt AES 256 CBC with a given IV
|
||||||
|
export function aesEncrypWithIV(buffer: Buffer, key: Buffer, IV: Buffer) {
|
||||||
|
const aes = createCipheriv('aes-256-cbc', key, IV)
|
||||||
|
return Buffer.concat([aes.update(buffer), aes.final()]) // prefix IV to the buffer
|
||||||
|
}
|
||||||
|
// sign HMAC using SHA 256
|
||||||
|
export function hmacSign(buffer: Buffer | Uint8Array, key: Buffer | Uint8Array, variant: 'sha256' | 'sha512' = 'sha256') {
|
||||||
|
return createHmac(variant, key).update(buffer).digest()
|
||||||
|
}
|
||||||
|
export function sha256(buffer: Buffer) {
|
||||||
|
return createHash('sha256').update(buffer).digest()
|
||||||
|
}
|
||||||
|
// HKDF key expansion
|
||||||
|
// from: https://github.com/benadida/node-hkdf
|
||||||
|
export function hkdf(buffer: Buffer, expandedLength: number, { info, salt }: { salt?: Buffer, info?: string }) {
|
||||||
|
const hashAlg = 'sha256'
|
||||||
|
const hashLength = 32
|
||||||
|
salt = salt || Buffer.alloc(hashLength)
|
||||||
|
// now we compute the PRK
|
||||||
|
const prk = createHmac(hashAlg, salt).update(buffer).digest()
|
||||||
|
|
||||||
|
let prev = Buffer.from([])
|
||||||
|
const buffers = []
|
||||||
|
const num_blocks = Math.ceil(expandedLength / hashLength)
|
||||||
|
|
||||||
|
const infoBuff = Buffer.from(info || [])
|
||||||
|
|
||||||
|
for (var i=0; i<num_blocks; i++) {
|
||||||
|
const hmac = createHmac(hashAlg, prk)
|
||||||
|
// XXX is there a more optimal way to build up buffers?
|
||||||
|
const input = Buffer.concat([
|
||||||
|
prev,
|
||||||
|
infoBuff,
|
||||||
|
Buffer.from(String.fromCharCode(i + 1))
|
||||||
|
]);
|
||||||
|
hmac.update(input)
|
||||||
|
|
||||||
|
prev = hmac.digest()
|
||||||
|
buffers.push(prev)
|
||||||
|
}
|
||||||
|
return Buffer.concat(buffers, expandedLength)
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Boom } from '@hapi/boom'
|
import { Boom } from '@hapi/boom'
|
||||||
import CurveCrypto from 'libsignal/src/curve25519_wrapper'
|
import { randomBytes } from 'crypto'
|
||||||
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto'
|
|
||||||
import { platform, release } from 'os'
|
import { platform, release } from 'os'
|
||||||
import { KeyPair } from '../Types'
|
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import { Binary } from '../WABinary'
|
import { Binary } from '../WABinary'
|
||||||
|
|
||||||
@@ -71,46 +69,6 @@ export const encodeWAMessage = (message: proto.IMessage) => (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const generateCurveKeyPair = (): KeyPair => {
|
|
||||||
const { pubKey, privKey } = CurveCrypto.keyPair(randomBytes(32))
|
|
||||||
return {
|
|
||||||
private: Buffer.from(privKey),
|
|
||||||
public: Buffer.from(pubKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateSharedKey = (privateKey: Uint8Array, publicKey: Uint8Array) => {
|
|
||||||
const shared = CurveCrypto.sharedSecret(publicKey, privateKey)
|
|
||||||
return Buffer.from(shared)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const curveSign = (privateKey: Uint8Array, buf: Uint8Array) => (
|
|
||||||
Buffer.from(CurveCrypto.sign(privateKey, buf))
|
|
||||||
)
|
|
||||||
|
|
||||||
export const curveVerify = (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
|
|
||||||
try {
|
|
||||||
CurveCrypto.verify(pubKey, message, signature)
|
|
||||||
return true
|
|
||||||
} catch(error) {
|
|
||||||
if(error.message.includes('Invalid')) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const signedKeyPair = (keyPair: KeyPair, keyId: number) => {
|
|
||||||
const signKeys = generateCurveKeyPair()
|
|
||||||
const pubKey = new Uint8Array(33)
|
|
||||||
pubKey.set([5], 0)
|
|
||||||
pubKey.set(signKeys.public, 1)
|
|
||||||
|
|
||||||
const signature = curveSign(keyPair.private, pubKey)
|
|
||||||
|
|
||||||
return { keyPair: signKeys, signature, keyId }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateRegistrationId = () => (
|
export const generateRegistrationId = () => (
|
||||||
Uint16Array.from(randomBytes(2))[0] & 0x3fff
|
Uint16Array.from(randomBytes(2))[0] & 0x3fff
|
||||||
)
|
)
|
||||||
@@ -134,9 +92,6 @@ export const encodeBigEndian = (e: number, t=4) => {
|
|||||||
|
|
||||||
export const toNumber = (t: Long | number) => (typeof t?.['low'] !== 'undefined' ? t['low'] : t) as number
|
export const toNumber = (t: Long | number) => (typeof t?.['low'] !== 'undefined' ? t['low'] : t) as number
|
||||||
|
|
||||||
export const whatsappID = (jid: string) => jid?.replace ('@c.us', '@s.whatsapp.net')
|
|
||||||
export const isGroupID = (jid: string) => jid?.endsWith ('@g.us')
|
|
||||||
|
|
||||||
export function shallowChanges <T> (old: T, current: T, {lookForDeletedKeys}: {lookForDeletedKeys: boolean}): Partial<T> {
|
export function shallowChanges <T> (old: T, current: T, {lookForDeletedKeys}: {lookForDeletedKeys: boolean}): Partial<T> {
|
||||||
let changes: Partial<T> = {}
|
let changes: Partial<T> = {}
|
||||||
for (let key in current) {
|
for (let key in current) {
|
||||||
@@ -153,64 +108,6 @@ export function shallowChanges <T> (old: T, current: T, {lookForDeletedKeys}: {l
|
|||||||
}
|
}
|
||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
/** decrypt AES 256 CBC; where the IV is prefixed to the buffer */
|
|
||||||
export function aesDecrypt(buffer: Buffer, key: Buffer) {
|
|
||||||
return aesDecryptWithIV(buffer.slice(16, buffer.length), key, buffer.slice(0, 16))
|
|
||||||
}
|
|
||||||
/** decrypt AES 256 CBC */
|
|
||||||
export function aesDecryptWithIV(buffer: Buffer, key: Buffer, IV: Buffer) {
|
|
||||||
const aes = createDecipheriv('aes-256-cbc', key, IV)
|
|
||||||
return Buffer.concat([aes.update(buffer), aes.final()])
|
|
||||||
}
|
|
||||||
// encrypt AES 256 CBC; where a random IV is prefixed to the buffer
|
|
||||||
export function aesEncrypt(buffer: Buffer | Uint8Array, key: Buffer) {
|
|
||||||
const IV = randomBytes(16)
|
|
||||||
const aes = createCipheriv('aes-256-cbc', key, IV)
|
|
||||||
return Buffer.concat([IV, aes.update(buffer), aes.final()]) // prefix IV to the buffer
|
|
||||||
}
|
|
||||||
// encrypt AES 256 CBC with a given IV
|
|
||||||
export function aesEncrypWithIV(buffer: Buffer, key: Buffer, IV: Buffer) {
|
|
||||||
const aes = createCipheriv('aes-256-cbc', key, IV)
|
|
||||||
return Buffer.concat([aes.update(buffer), aes.final()]) // prefix IV to the buffer
|
|
||||||
}
|
|
||||||
// sign HMAC using SHA 256
|
|
||||||
export function hmacSign(buffer: Buffer | Uint8Array, key: Buffer | Uint8Array, variant: 'sha256' | 'sha512' = 'sha256') {
|
|
||||||
return createHmac(variant, key).update(buffer).digest()
|
|
||||||
}
|
|
||||||
export function sha256(buffer: Buffer) {
|
|
||||||
return createHash('sha256').update(buffer).digest()
|
|
||||||
}
|
|
||||||
// HKDF key expansion
|
|
||||||
// from: https://github.com/benadida/node-hkdf
|
|
||||||
export function hkdf(buffer: Buffer, expandedLength: number, { info, salt }: { salt?: Buffer, info?: string }) {
|
|
||||||
const hashAlg = 'sha256'
|
|
||||||
const hashLength = 32
|
|
||||||
salt = salt || Buffer.alloc(hashLength)
|
|
||||||
// now we compute the PRK
|
|
||||||
const prk = createHmac(hashAlg, salt).update(buffer).digest()
|
|
||||||
|
|
||||||
let prev = Buffer.from([])
|
|
||||||
const buffers = []
|
|
||||||
const num_blocks = Math.ceil(expandedLength / hashLength)
|
|
||||||
|
|
||||||
const infoBuff = Buffer.from(info || [])
|
|
||||||
|
|
||||||
for (var i=0; i<num_blocks; i++) {
|
|
||||||
const hmac = createHmac(hashAlg, prk)
|
|
||||||
// XXX is there a more optimal way to build up buffers?
|
|
||||||
const input = Buffer.concat([
|
|
||||||
prev,
|
|
||||||
infoBuff,
|
|
||||||
Buffer.from(String.fromCharCode(i + 1))
|
|
||||||
]);
|
|
||||||
hmac.update(input)
|
|
||||||
|
|
||||||
prev = hmac.digest()
|
|
||||||
buffers.push(prev)
|
|
||||||
}
|
|
||||||
return Buffer.concat(buffers, expandedLength)
|
|
||||||
}
|
|
||||||
/** unix timestamp of a date in seconds */
|
/** unix timestamp of a date in seconds */
|
||||||
export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime()/1000)
|
export const unixTimestampSeconds = (date: Date = new Date()) => Math.floor(date.getTime()/1000)
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export * from './decode-wa-message'
|
|||||||
export * from './generics'
|
export * from './generics'
|
||||||
export * from './messages'
|
export * from './messages'
|
||||||
export * from './messages-media'
|
export * from './messages-media'
|
||||||
export * from './validate-connection'
|
export * from './validate-connection'
|
||||||
|
export * from './crypto'
|
||||||
@@ -11,7 +11,8 @@ import { join } from 'path'
|
|||||||
import { once } from 'events'
|
import { once } from 'events'
|
||||||
import got, { Options, Response } from 'got'
|
import got, { Options, Response } from 'got'
|
||||||
import { MessageType, WAMessageContent, WAProto, WAGenericMediaMessage, WAMediaUpload, MediaType } from '../Types'
|
import { MessageType, WAMessageContent, WAProto, WAGenericMediaMessage, WAMediaUpload, MediaType } from '../Types'
|
||||||
import { generateMessageID, hkdf } from './generics'
|
import { generateMessageID } from './generics'
|
||||||
|
import { hkdf } from './crypto'
|
||||||
import { DEFAULT_ORIGIN } from '../Defaults'
|
import { DEFAULT_ORIGIN } from '../Defaults'
|
||||||
|
|
||||||
export const hkdfInfoKey = (type: MediaType) => {
|
export const hkdfInfoKey = (type: MediaType) => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
MediaType,
|
MediaType,
|
||||||
WAMessageStatus
|
WAMessageStatus
|
||||||
} from "../Types"
|
} from "../Types"
|
||||||
import { generateMessageID, unixTimestampSeconds, whatsappID } from "./generics"
|
import { generateMessageID, unixTimestampSeconds } from "./generics"
|
||||||
import { encryptedStream, generateThumbnail, getAudioDuration } from "./messages-media"
|
import { encryptedStream, generateThumbnail, getAudioDuration } from "./messages-media"
|
||||||
|
|
||||||
type MediaUploadData = {
|
type MediaUploadData = {
|
||||||
@@ -268,8 +268,6 @@ export const generateWAMessageFromContent = (
|
|||||||
options: MessageGenerationOptionsFromContent
|
options: MessageGenerationOptionsFromContent
|
||||||
) => {
|
) => {
|
||||||
if (!options.timestamp) options.timestamp = new Date() // set timestamp to now
|
if (!options.timestamp) options.timestamp = new Date() // set timestamp to now
|
||||||
// prevent an annoying bug (WA doesn't accept sending messages with '@c.us')
|
|
||||||
jid = whatsappID(jid)
|
|
||||||
|
|
||||||
const key = Object.keys(message)[0]
|
const key = Object.keys(message)[0]
|
||||||
const timestamp = unixTimestampSeconds(options.timestamp)
|
const timestamp = unixTimestampSeconds(options.timestamp)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { sha256, generateSharedKey, hkdf } from "./generics";
|
import { sha256, Curve, hkdf } from "./crypto";
|
||||||
import { Binary } from "../WABinary";
|
import { Binary } from "../WABinary";
|
||||||
import { createCipheriv, createDecipheriv } from "crypto";
|
import { createCipheriv, createDecipheriv } from "crypto";
|
||||||
import { NOISE_MODE, NOISE_WA_HEADER } from "../Defaults";
|
import { NOISE_MODE, NOISE_WA_HEADER } from "../Defaults";
|
||||||
@@ -100,10 +100,10 @@ export default ({ public: publicKey, private: privateKey }: KeyPair) => {
|
|||||||
finishInit,
|
finishInit,
|
||||||
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
|
processHandshake: ({ serverHello }: proto.HandshakeMessage, noiseKey: KeyPair) => {
|
||||||
authenticate(serverHello!.ephemeral!)
|
authenticate(serverHello!.ephemeral!)
|
||||||
mixIntoKey(generateSharedKey(privateKey, serverHello.ephemeral!))
|
mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral!))
|
||||||
|
|
||||||
const decStaticContent = decrypt(serverHello!.static!)
|
const decStaticContent = decrypt(serverHello!.static!)
|
||||||
mixIntoKey(generateSharedKey(privateKey, decStaticContent))
|
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent))
|
||||||
|
|
||||||
const certDecoded = decrypt(serverHello!.payload!)
|
const certDecoded = decrypt(serverHello!.payload!)
|
||||||
const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded)
|
const { details: certDetails, signature: certSignature } = proto.NoiseCertificate.decode(certDecoded)
|
||||||
@@ -115,7 +115,7 @@ export default ({ public: publicKey, private: privateKey }: KeyPair) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keyEnc = encrypt(noiseKey.public)
|
const keyEnc = encrypt(noiseKey.public)
|
||||||
mixIntoKey(generateSharedKey(noiseKey.private, serverHello!.ephemeral!))
|
mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello!.ephemeral!))
|
||||||
|
|
||||||
return keyEnc
|
return keyEnc
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as libsignal from 'libsignal'
|
import * as libsignal from 'libsignal'
|
||||||
import { encodeBigEndian, generateCurveKeyPair } from "./generics"
|
import { encodeBigEndian } from "./generics"
|
||||||
|
import { Curve } from "./crypto"
|
||||||
import { SenderKeyDistributionMessage, GroupSessionBuilder, SenderKeyRecord, SenderKeyName, GroupCipher } from '../../WASignalGroup'
|
import { SenderKeyDistributionMessage, GroupSessionBuilder, SenderKeyRecord, SenderKeyName, GroupCipher } from '../../WASignalGroup'
|
||||||
import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, AuthenticationState } from "../Types/Auth"
|
import { SignalIdentity, SignalKeyStore, SignedKeyPair, KeyPair, AuthenticationState } from "../Types/Auth"
|
||||||
import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildUInt, jidDecode } from "../WABinary"
|
import { assertNodeErrorFree, BinaryNode, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildUInt, jidDecode } from "../WABinary"
|
||||||
@@ -48,7 +49,7 @@ export const generateOrGetPreKeys = ({ creds }: AuthenticationState, range: numb
|
|||||||
const newPreKeys: { [id: number]: KeyPair } = { }
|
const newPreKeys: { [id: number]: KeyPair } = { }
|
||||||
if(remaining > 0) {
|
if(remaining > 0) {
|
||||||
for(let i = creds.nextPreKeyId;i <= lastPreKeyId;i++) {
|
for(let i = creds.nextPreKeyId;i <= lastPreKeyId;i++) {
|
||||||
newPreKeys[i] = generateCurveKeyPair()
|
newPreKeys[i] = Curve.generateKeyPair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { Boom } from '@hapi/boom'
|
|||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import { proto } from '../../WAProto'
|
import { proto } from '../../WAProto'
|
||||||
import type { AuthenticationState, SocketConfig, SignalKeyStore, AuthenticationCreds, KeyPair } from "../Types"
|
import type { AuthenticationState, SocketConfig, SignalKeyStore, AuthenticationCreds, KeyPair } from "../Types"
|
||||||
import { curveSign, hmacSign, curveVerify, encodeInt, generateCurveKeyPair, generateRegistrationId, signedKeyPair } from './generics'
|
import { Curve, hmacSign, signedKeyPair } from './crypto'
|
||||||
|
import { encodeInt, generateRegistrationId } from './generics'
|
||||||
import { BinaryNode, S_WHATSAPP_NET, jidDecode, Binary } from '../WABinary'
|
import { BinaryNode, S_WHATSAPP_NET, jidDecode, Binary } from '../WABinary'
|
||||||
import { createSignalIdentity } from './signal'
|
import { createSignalIdentity } from './signal'
|
||||||
|
|
||||||
@@ -116,10 +117,10 @@ export const initInMemoryKeyStore = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const initAuthState = (): AuthenticationState => {
|
export const initAuthState = (): AuthenticationState => {
|
||||||
const identityKey = generateCurveKeyPair()
|
const identityKey = Curve.generateKeyPair()
|
||||||
return {
|
return {
|
||||||
creds: {
|
creds: {
|
||||||
noiseKey: generateCurveKeyPair(),
|
noiseKey: Curve.generateKeyPair(),
|
||||||
signedIdentityKey: identityKey,
|
signedIdentityKey: identityKey,
|
||||||
signedPreKey: signedKeyPair(identityKey, 1),
|
signedPreKey: signedKeyPair(identityKey, 1),
|
||||||
registrationId: generateRegistrationId(),
|
registrationId: generateRegistrationId(),
|
||||||
@@ -158,12 +159,12 @@ export const configureSuccessfulPairing = (
|
|||||||
const { accountSignatureKey, accountSignature } = account
|
const { accountSignatureKey, accountSignature } = account
|
||||||
|
|
||||||
const accountMsg = Binary.build(new Uint8Array([6, 0]), account.details, signedIdentityKey.public).readByteArray()
|
const accountMsg = Binary.build(new Uint8Array([6, 0]), account.details, signedIdentityKey.public).readByteArray()
|
||||||
if (!curveVerify(accountSignatureKey, accountMsg, accountSignature)) {
|
if (!Curve.verify(accountSignatureKey, accountMsg, accountSignature)) {
|
||||||
throw new Boom('Failed to verify account signature')
|
throw new Boom('Failed to verify account signature')
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceMsg = Binary.build(new Uint8Array([6, 1]), account.details, signedIdentityKey.public, account.accountSignatureKey).readByteArray()
|
const deviceMsg = Binary.build(new Uint8Array([6, 1]), account.details, signedIdentityKey.public, account.accountSignatureKey).readByteArray()
|
||||||
account.deviceSignature = curveSign(signedIdentityKey.private, deviceMsg)
|
account.deviceSignature = Curve.sign(signedIdentityKey.private, deviceMsg)
|
||||||
|
|
||||||
const identity = createSignalIdentity(jid, accountSignatureKey)
|
const identity = createSignalIdentity(jid, accountSignatureKey)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user