mirror of
https://github.com/FranP-code/Baileys.git
synced 2025-10-13 00:32:22 +00:00
Moved to src
This commit is contained in:
164
src/WAConnection/Validation.ts
Normal file
164
src/WAConnection/Validation.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import * as Curve from 'curve25519-js'
|
||||
import * as Utils from './Utils'
|
||||
import WAConnectionBase from './Base'
|
||||
|
||||
export default class WAConnectionValidator extends WAConnectionBase {
|
||||
/** Authenticate the connection */
|
||||
protected async authenticate() {
|
||||
if (!this.authInfo.clientID) {
|
||||
// if no auth info is present, that is, a new session has to be established
|
||||
// generate a client ID
|
||||
this.authInfo = {
|
||||
clientID: Utils.generateClientID(),
|
||||
clientToken: null,
|
||||
serverToken: null,
|
||||
encKey: null,
|
||||
macKey: null,
|
||||
}
|
||||
}
|
||||
|
||||
const data = ['admin', 'init', this.version, this.browserDescription, this.authInfo.clientID, true]
|
||||
return this.query(data)
|
||||
.then((json) => {
|
||||
// we're trying to establish a new connection or are trying to log in
|
||||
switch (json.status) {
|
||||
case 200: // all good and we can procede to generate a QR code for new connection, or can now login given present auth info
|
||||
if (this.authInfo.encKey && this.authInfo.macKey) {
|
||||
// if we have the info to restore a closed session
|
||||
const data = [
|
||||
'admin',
|
||||
'login',
|
||||
this.authInfo.clientToken,
|
||||
this.authInfo.serverToken,
|
||||
this.authInfo.clientID,
|
||||
'takeover',
|
||||
]
|
||||
return this.query(data, null, null, 's1') // wait for response with tag "s1"
|
||||
} else {
|
||||
return this.generateKeysForAuth(json.ref)
|
||||
}
|
||||
default:
|
||||
throw [json.status, 'unknown error', json]
|
||||
}
|
||||
})
|
||||
.then((json) => {
|
||||
switch (json.status) {
|
||||
case 401: // if the phone was unpaired
|
||||
throw [json.status, 'unpaired from phone', json]
|
||||
case 429: // request to login was denied, don't know why it happens
|
||||
throw [json.status, 'request denied, try reconnecting', json]
|
||||
case 304: // request to generate a new key for a QR code was denied
|
||||
throw [json.status, 'request for new key denied', json]
|
||||
default:
|
||||
break
|
||||
}
|
||||
if (json[1] && json[1].challenge) {
|
||||
// if its a challenge request (we get it when logging in)
|
||||
return this.respondToChallenge(json[1].challenge).then((json) => {
|
||||
if (json.status !== 200) {
|
||||
// throw an error if the challenge failed
|
||||
throw [json.status, 'unknown error', json]
|
||||
}
|
||||
return this.waitForMessage('s2', []) // otherwise wait for the validation message
|
||||
})
|
||||
} else {
|
||||
// otherwise just chain the promise further
|
||||
return json
|
||||
}
|
||||
})
|
||||
.then((json) => {
|
||||
this.validateNewConnection(json[1]) // validate the connection
|
||||
this.log('validated connection successfully')
|
||||
this.lastSeen = new Date() // set last seen to right now
|
||||
return this.userMetaData
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Once the QR code is scanned and we can validate our connection, or we resolved the challenge when logging back in
|
||||
* @private
|
||||
* @param {object} json
|
||||
*/
|
||||
private validateNewConnection(json) {
|
||||
const onValidationSuccess = () => {
|
||||
// set metadata: one's WhatsApp ID [cc][number]@s.whatsapp.net, name on WhatsApp, info about the phone
|
||||
this.userMetaData = {
|
||||
id: json.wid.replace('@c.us', '@s.whatsapp.net'),
|
||||
name: json.pushname,
|
||||
phone: json.phone,
|
||||
}
|
||||
return this.userMetaData
|
||||
}
|
||||
|
||||
if (json.connected) {
|
||||
// only if we're connected
|
||||
if (!json.secret) {
|
||||
// if we didn't get a secret, we don't need it, we're validated
|
||||
return onValidationSuccess()
|
||||
}
|
||||
const secret = Buffer.from(json.secret, 'base64')
|
||||
if (secret.length !== 144) {
|
||||
throw [4, 'incorrect secret length: ' + secret.length]
|
||||
}
|
||||
// generate shared key from our private key & the secret shared by the server
|
||||
const sharedKey = Curve.sharedKey(this.curveKeys.private, secret.slice(0, 32))
|
||||
// expand the key to 80 bytes using HKDF
|
||||
const expandedKey = Utils.hkdf(sharedKey as Buffer, 80)
|
||||
|
||||
// perform HMAC validation.
|
||||
const hmacValidationKey = expandedKey.slice(32, 64)
|
||||
const hmacValidationMessage = Buffer.concat([secret.slice(0, 32), secret.slice(64, secret.length)])
|
||||
|
||||
const hmac = Utils.hmacSign(hmacValidationMessage, hmacValidationKey)
|
||||
|
||||
if (hmac.equals(secret.slice(32, 64))) {
|
||||
// computed HMAC should equal secret[32:64]
|
||||
// expandedKey[64:] + secret[64:] are the keys, encrypted using AES, that are used to encrypt/decrypt the messages recieved from WhatsApp
|
||||
// they are encrypted using key: expandedKey[0:32]
|
||||
const encryptedAESKeys = Buffer.concat([
|
||||
expandedKey.slice(64, expandedKey.length),
|
||||
secret.slice(64, secret.length),
|
||||
])
|
||||
const decryptedKeys = Utils.aesDecrypt(encryptedAESKeys, expandedKey.slice(0, 32))
|
||||
// set the credentials
|
||||
this.authInfo = {
|
||||
encKey: decryptedKeys.slice(0, 32), // first 32 bytes form the key to encrypt/decrypt messages
|
||||
macKey: decryptedKeys.slice(32, 64), // last 32 bytes from the key to sign messages
|
||||
clientToken: json.clientToken,
|
||||
serverToken: json.serverToken,
|
||||
clientID: this.authInfo.clientID,
|
||||
}
|
||||
return onValidationSuccess()
|
||||
} else {
|
||||
// if the checksums didn't match
|
||||
throw [5, 'HMAC validation failed']
|
||||
}
|
||||
} else {
|
||||
// if we didn't get the connected field (usually we get this message when one opens WhatsApp on their phone)
|
||||
throw [6, 'json connection failed', json]
|
||||
}
|
||||
}
|
||||
/**
|
||||
* When logging back in (restoring a previously closed session), WhatsApp may challenge one to check if one still has the encryption keys
|
||||
* WhatsApp does that by asking for us to sign a string it sends with our macKey
|
||||
*/
|
||||
protected respondToChallenge(challenge: string) {
|
||||
const bytes = Buffer.from(challenge, 'base64') // decode the base64 encoded challenge string
|
||||
const signed = Utils.hmacSign(bytes, this.authInfo.macKey).toString('base64') // sign the challenge string with our macKey
|
||||
const data = ['admin', 'challenge', signed, this.authInfo.serverToken, this.authInfo.clientID] // prepare to send this signed string with the serverToken & clientID
|
||||
this.log('resolving login challenge')
|
||||
return this.query(data)
|
||||
}
|
||||
/**
|
||||
* When starting a new session, generate a QR code by generating a private/public key pair & the keys the server sends
|
||||
* @private
|
||||
*/
|
||||
protected generateKeysForAuth(ref: string) {
|
||||
this.curveKeys = Curve.generateKeyPair(Utils.randomBytes(32))
|
||||
this.onReadyForPhoneAuthentication([
|
||||
ref,
|
||||
Buffer.from(this.curveKeys.public).toString('base64'),
|
||||
this.authInfo.clientID,
|
||||
])
|
||||
return this.waitForMessage('s1', [])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user