UNPKG

@tgsnake/core

Version:

Pure Telegram MTProto library for nodejs

124 lines (123 loc) 5.32 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.kdf = kdf; exports.pack = pack; exports.unpack = unpack; const platform_node_js_1 = require("../platform.node.js"); const index_js_1 = require("../errors/index.js"); const index_js_2 = require("../raw/index.js"); const MsgId_js_1 = require("../session/internals/MsgId.js"); const helpers_js_1 = require("../helpers.js"); const Aes_js_1 = require("./Aes.js"); const Logger_js_1 = require("../Logger.js"); const STORED_MSG_IDS_MAX_SIZE = 1000 * 2; function sha256(data) { const hash = platform_node_js_1.crypto.createHash('sha256'); hash.update(data); return hash.digest(); } function toBytes(value) { const bytesArray = []; for (let i = 0; i < 8; i++) { let shift = value >> BigInt(8 * i); shift &= BigInt(255); bytesArray[i] = Number(String(shift)); } return platform_node_js_1.Buffer.from(bytesArray); } function kdf(authKey, msgKey, outgoing) { const x = outgoing ? 0 : 8; const sha256A = sha256(platform_node_js_1.Buffer.concat([ msgKey, authKey.subarray(x, x + 36), ])); const sha256B = sha256(platform_node_js_1.Buffer.concat([ authKey.subarray(x + 40, x + 76), msgKey, ])); const aesKey = platform_node_js_1.Buffer.concat([ sha256A.subarray(0, 8), sha256B.subarray(8, 24), sha256A.subarray(24, 32), ]); const aesIv = platform_node_js_1.Buffer.concat([ sha256B.subarray(0, 8), sha256A.subarray(8, 24), sha256B.subarray(24, 32), ]); return [aesKey, aesIv]; } function pack(message, salt, sessionId, authKey, authKeyId) { const data = platform_node_js_1.Buffer.concat([ platform_node_js_1.Buffer.concat([ toBytes(salt), sessionId, ]), message.write(), ]); const padding = platform_node_js_1.Buffer.from(platform_node_js_1.crypto.randomBytes((0, helpers_js_1.mod)(-(platform_node_js_1.Buffer.byteLength(data) + 12), 16) + 12)); const msgKeyLarge = sha256(platform_node_js_1.Buffer.concat([ authKey.subarray(88, 88 + 32), data, padding, ])); const msgKey = msgKeyLarge.subarray(8, 24); const [aesKey, aesIv] = kdf(authKey, msgKey, true); return platform_node_js_1.Buffer.concat([ authKeyId, msgKey, (0, Aes_js_1.ige256Encrypt)(platform_node_js_1.Buffer.concat([data, padding]), aesKey, aesIv), ]); } async function unpack(b, sessionId, authKey, authKeyId, storedMsgId) { index_js_1.SecurityCheckMismatch.check(b.read(8).equals(authKeyId), 'Provided auth key id is not equal with expected one.'); const msgKey = b.read(16); const [aesKey, aesIv] = kdf(authKey, msgKey, false); const encrypted = b.read(); const decrypted = (0, Aes_js_1.ige256Decrypt)(encrypted, aesKey, aesIv); const hash = sha256(platform_node_js_1.Buffer.concat([ authKey.subarray(96, 96 + 32), decrypted, ])); index_js_1.SecurityCheckMismatch.check(msgKey.equals(hash.subarray(8, 24)), 'Provided msg key is not equal with expected one'); const data = new index_js_2.BytesIO(decrypted); data.read(8); index_js_1.SecurityCheckMismatch.check(platform_node_js_1.Buffer.from(data.read(8)).equals(sessionId), 'Provided session id is not equal with expected one.'); const message = await index_js_2.Message.read(new index_js_2.BytesIO(data.buffer.slice(16))).catch((error) => { Logger_js_1.Logger.error(error); }); data.seek(32); const payload = data.read(); const padding = payload.subarray(message.length); index_js_1.SecurityCheckMismatch.check(platform_node_js_1.Buffer.byteLength(padding) >= 12 && platform_node_js_1.Buffer.byteLength(padding) <= 1024, 'Payload padding is lower than 12 or bigger than 1024'); index_js_1.SecurityCheckMismatch.check((0, helpers_js_1.mod)(platform_node_js_1.Buffer.byteLength(padding), 4) === 0, 'Mod of padding length with 4 is equal with zero'); index_js_1.SecurityCheckMismatch.check((0, helpers_js_1.bigIntMod)(message.msgId, BigInt(2)) !== BigInt(0), 'Mod of msgId with 2 is not equal with zero'); if (storedMsgId.length > STORED_MSG_IDS_MAX_SIZE) { storedMsgId.splice(0, Math.floor(STORED_MSG_IDS_MAX_SIZE / 2)); } if (storedMsgId.length) { if (message.msgId < storedMsgId[0]) { throw new index_js_1.SecurityCheckMismatch('Msg id is lower than all of the stored values'); } if (storedMsgId.includes(message.msgId)) { throw new index_js_1.SecurityCheckMismatch('Msg id is equal to any of the stored values'); } const msgId = new MsgId_js_1.MsgId(); const timeDiff = BigInt(message.msgId - msgId.getMsgId()) / BigInt(2 ** 32); if (timeDiff > BigInt(30)) { throw new index_js_1.SecurityCheckMismatch('Msg id belongs over 30 seconds in the future'); } if (timeDiff < BigInt(-300)) { throw new index_js_1.SecurityCheckMismatch('Msg id belongs over 300 seconds in the past'); } } storedMsgId.push(message.msgId); storedMsgId.sort((a, b) => { if (a > b) return 1; if (a < b) return -1; return 0; }); return message; }